For traditional barcodes, we assembled the genome of each sample, and
then used BLAST to search for each of the traditional barcode genes. We
recorded if we could find this gene in the assembly, coding as missing
data if we could not. We then recorded whether the best BLAST hit for a
sample was the correct species.
VarKoder
For VarKoder, we used leave-one-out cross-validation to test the
accuracy for family, genera, species in the joint
Malpighiaceae-Chrysobalanaceae dataset. We used as input data varKodes
produced from kmers of size 7 and 500Kbp to 200Mbp of data, or all of
the data available if less than 200 Mbp. For each sample, we built a
model using as input data from all other samples. Then we queried the
sample left out, using as input the images generated from 500Kb to the
total data available. Now we will summarize the results.
Accuracy vs data amount and taxonomic levels
In this test, we used varKoder v0.6.0.
Let’s process the results.
read_and_process_xval = function(infolder){
plan(multisession(workers = 12))
varkoder_results = list.files(infolder,
'predictions.csv',
recursive=T,
full.names = T) %>%
furrr::future_map_dfr(~read_csv(.x) %>% mutate(sample_id = as.character(sample_id))) %>%
select(-1) %>%
filter(str_detect(query_basepairs,'^0+[125]0+K$')) %>% #we will ignore queries that are not standardized sizes
rename(query_bp = query_basepairs) %>%
mutate(quality_included = T)
plan(sequential)
all_taxlabels = str_remove(varkoder_results$actual_labels,";*low_quality:True;*") %>% str_split(';') %>% unlist %>% unique
varkoder_results = varkoder_results %>%
mutate(query_labels = str_remove(actual_labels,";*low_quality:True;*") %>% str_split(';'),
predicted_list = str_split(predicted_labels,';')
) %>%
rowwise() %>%
mutate(family_correct = query_labels[str_detect(query_labels,'family')] %in% predicted_list,
genus_correct = query_labels[str_detect(query_labels,'genus')] %in% predicted_list,
species_correct = ifelse(any(str_detect(query_labels,'species')),
query_labels[str_detect(query_labels,'species')] %in% predicted_list,
NA
),
family_incorrect = any(!(predicted_list[str_detect(predicted_list,'family')] %in% query_labels[str_detect(query_labels,'family')])),
genus_incorrect = any(!(predicted_list[str_detect(predicted_list,'genus')] %in% query_labels[str_detect(query_labels,'genus')])),
species_incorrect = ifelse(any(str_detect(query_labels,'species')),
any(!(predicted_list[str_detect(predicted_list,'species')] %in% query_labels[str_detect(query_labels,'species')])),
NA
)
)
return(varkoder_results)
}
summarize_results = function(res,level){
res = res %>%
ungroup() %>%
mutate(low_quality = str_detect(actual_labels,"low_quality:True"),
result = as.character(ifelse(res[,str_c(level,'correct',sep='_')] & !res[,str_c(level,'incorrect',sep='_')], 'correct',
ifelse(res[,str_c(level,'correct',sep='_')] & res[,str_c(level,'incorrect',sep='_')], 'ambiguous',
ifelse(!res[,str_c(level,'correct',sep='_')] & res[,str_c(level,'incorrect',sep='_')], 'incorrect',
'inconclusive'
))))
) %>%
filter(!is.na(result)) %>%
group_by(query_bp,result) %>%
summarise(N=n(), .groups = 'drop') %>%
group_by(query_bp) %>%
mutate(p= N/sum(N)) %>%
mutate(query_bp = as.integer(str_remove(query_bp,'K'))*1000) %>%
ungroup() %>%
mutate(query_bp = as.factor(query_bp)) %>%
complete(query_bp,result, fill = list(p = 0, N = 0)) %>%
mutate(query_bp = as.numeric(as.character(query_bp))) %>%
ungroup()
return(res)
}
plot_area = function(sum_df, title, relative = FALSE, grid = TRUE){
breaks = c(500000,
1000000,
2000000,
5000000,
10000000,
20000000,
50000000,
100000000,
200000000
)
xlimits = range(breaks)
sum_df = sum_df %>%
mutate(result = factor(result,ordered = T, levels = c('correct','ambiguous','inconclusive','incorrect')))
if (relative){
ylimits = c(0,1)
} else {
ylimits = c(0,sum_df %>% group_by(query_bp) %>% summarize(N=sum(N)) %>% pull(N) %>% max)
}
# Get colors from a Color Brewer palette
brewer_colors <- RColorBrewer::brewer.pal(4, "Accent")
if (relative) {
p1 = ggplot(sum_df, aes(x=query_bp,y=p,fill=result)) +
geom_area(position='stack') +
scale_fill_manual(values = setNames(brewer_colors, c("correct", "ambiguous", "inconclusive", "incorrect"))) +
scale_alpha_manual(values=c(0.5,1)) +
scale_x_log10(labels = scales::label_number(scale_cut = scales::cut_si('bp')),breaks = breaks) +
scale_y_continuous() +
ggtitle(title) +
ylab('Fraction of samples') +
xlab('Base pairs in query images') +
theme_few() +
theme(axis.text.x = element_text(hjust=1,angle=45))
} else {
p1 = ggplot(sum_df, aes(x=query_bp,y=N,fill=result)) +
geom_area(position='stack') +
scale_fill_manual(values = setNames(brewer_colors, c("correct", "ambiguous", "inconclusive", "incorrect"))) +
scale_alpha_manual(values=c(0.5,1)) +
scale_x_log10(labels = scales::label_number(scale_cut = scales::cut_si('bp')),breaks = breaks) +
scale_y_continuous() +
ggtitle(title) +
ylab('Number of samples') +
xlab('Base pairs in query images') +
theme_few() +
theme(axis.text.x = element_text(hjust=1,angle=45))
}
if (grid){
p1 = p1 +
scale_y_continuous(n.breaks = 10, minor_breaks = waiver()) +
theme(panel.background = element_rect(fill = NA),
panel.grid.major.y = element_line(colour = gray(0.5)),
panel.grid.minor.y = element_line(colour = gray(0.6),linetype = 2),
panel.ontop = TRUE)
}
p1 = p1 + coord_cartesian(xlim=xlimits, ylim=ylimits,expand = FALSE)
return(p1)
}
Now let’s plot genus-level accuracy for a model taking quality labels
into account:
results = read_and_process_xval('Malpighiaceae+Chrysobalanaceae/varKoder/vit_results/')
summary_genus = summarize_results(results,'genus')
p_genus = plot_area(summary_genus, 'varKoder genus', relative = TRUE)
p_genus

Now the same but with species
summary_species = summarize_results(results,'species')
p_species = plot_area(summary_species, 'varKoder species', relative = TRUE)
Scale for y is already present.
Adding another scale for y, which will replace the existing scale.
p_species

Finally, family
summary_family = summarize_results(results,'family')
p_family = plot_area(summary_family, 'varKoder family', relative = TRUE)
Scale for y is already present.
Adding another scale for y, which will replace the existing scale.
p_family

what explains the errors?
Now we will try to identify which samples failed and why they failed.
Particuarly, how do DNA quality, amount of data, and the number of
samples per class impact results? We will use genus-level predictions to
test.
genus_predictions = results %>%
mutate(predicted_genus = str_extract(predicted_labels, 'genus:[^;]*'),
actual_genus = str_extract(actual_labels, 'genus:[^;]*')) %>%
select(-starts_with('family'),-starts_with('species')) %>%
pivot_longer(cols = starts_with("genus"), names_to = "predicted_label", values_to = "confidence") %>%
filter(actual_genus == predicted_label) %>%
select(query_bp, sample_id, basefrequency_sd, actual_genus, confidence) %>%
mutate(query_bp = 1000*(str_remove(query_bp, "K") %>% as.integer))
genus_predictions = genus_predictions %>%
select(sample_id, actual_genus) %>%
distinct() %>%
group_by(actual_genus) %>%
summarise(N_samples = n()) %>%
right_join(genus_predictions)
Joining with `by = join_by(actual_genus)`
genus_predictions
Now let’s make some plots. First, what is the effect of number of
samples per class in confidence?
plot_genus_N_vs_conf = ggplot(genus_predictions, aes(x = N_samples-1,
y = confidence)) +
scale_color_viridis_c() +
geom_jitter(alpha=0.3) +
scale_x_log10() +
#ylab('Confidence in correct prediction\n(logit scale)') +
ylab('Confidence in correct prediction') +
xlab('Number of samples in correct genus\n(log scale)') +
#scale_y_continuous(trans = "logit", breaks = c(1e-4,0.001,0.01,0.1,0.25,0.5,0.75,0.9,0.99,0.999,1-1e-4)) +
scale_y_continuous(limits=c(0,1)) +
theme_few() +
theme(panel.grid.major.y = element_line(colour = gray(0.8)))
plot_genus_N_vs_conf

Now, what is the effect of sample quality in confidence?
plot_genus_freqsd_vs_conf = ggplot(genus_predictions, aes(x = basefrequency_sd, y = confidence)) +
geom_point(alpha=0.3) +
scale_x_log10() +
#scale_y_continuous(trans = "logit", breaks = c(1e-4,0.001,0.01,0.1,0.25,0.5,0.75,0.9,0.99,0.999,1-1e-4)) +
scale_y_continuous(limits=c(0,1)) +
#ylab('Confidence in correct prediction\n(logit scale)') +
ylab('Confidence in correct prediction') +
xlab('Standard deviation of base frequencies') +
theme_few() +
theme(panel.grid.major.y = element_line(colour = gray(0.8)))
plot_genus_freqsd_vs_conf

Now, what is the effect of amount of data in confidence?
plot_genus_bp_vs_conf = ggplot(genus_predictions, aes(x = query_bp, y = confidence)) +
geom_jitter(alpha=0.3) +
#scale_y_continuous(trans = "logit", breaks = c(1e-4,0.001,0.01,0.1,0.25,0.5,0.75,0.9,0.99,0.999,1-1e-4)) +
scale_y_continuous(limits=c(0,1)) +
#ylab('Confidence in correct prediction\n(logit scale)') +
ylab('Confidence in correct prediction') +
xlab('Base pairs in query images\n(log scale)') +
scale_x_log10() +
theme_few() +
theme(panel.grid.major.y = element_line(colour = gray(0.8)))
plot_genus_bp_vs_conf

Now let’s save the three of them as a single plot using cowplot.
combined_conf = patchwork::wrap_plots(plot_genus_N_vs_conf + theme(text = element_text(size=8)),
plot_genus_bp_vs_conf + theme(axis.title.y=element_blank(),
axis.text.y=element_blank(),
text = element_text(size=8)),
plot_genus_freqsd_vs_conf + theme(axis.title.y=element_blank(),
axis.text.y=element_blank(),
text = element_text(size=8))) +
patchwork::plot_annotation(tag_levels = 'A')
combined_conf
ggsave(filename = 'images_manuscript/supp_conf_predictors.pdf',device = 'pdf',width = 7,height=3,units = 'in',useDingbats=F)

Let’s put it all together now in a linear model:
lm_data = genus_predictions %>%
mutate(confidence = ifelse(confidence == 1, confidence-0.0000001, confidence),
confidence = car::logit(confidence)) %>%
mutate(query_bp = (query_bp - mean(query_bp))/sd(query_bp),
basefrequency_sd = (basefrequency_sd - mean(basefrequency_sd))/sd(basefrequency_sd),
N_samples = (N_samples - mean(N_samples))/sd(N_samples)
)
full_model = lm(formula = confidence~query_bp*basefrequency_sd*N_samples, data = lm_data)
full_model
Call:
lm(formula = confidence ~ query_bp * basefrequency_sd * N_samples,
data = lm_data)
Coefficients:
(Intercept) query_bp basefrequency_sd
4.79835 0.07638 -0.75108
N_samples query_bp:basefrequency_sd query_bp:N_samples
1.69980 -0.04625 -0.11011
basefrequency_sd:N_samples query_bp:basefrequency_sd:N_samples
-0.25966 -0.38270
summary(full_model)
Call:
lm(formula = confidence ~ query_bp * basefrequency_sd * N_samples,
data = lm_data)
Residuals:
Min 1Q Median 3Q Max
-17.3636 -0.9435 0.4652 1.4977 5.3559
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 4.79835 0.05705 84.110 < 2e-16 ***
query_bp 0.07638 0.07636 1.000 0.3173
basefrequency_sd -0.75108 0.10965 -6.850 9.49e-12 ***
N_samples 1.69980 0.06023 28.223 < 2e-16 ***
query_bp:basefrequency_sd -0.04625 0.19985 -0.231 0.8170
query_bp:N_samples -0.11011 0.08252 -1.334 0.1822
basefrequency_sd:N_samples -0.25966 0.13175 -1.971 0.0489 *
query_bp:basefrequency_sd:N_samples -0.38270 0.23793 -1.608 0.1079
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 2.351 on 2251 degrees of freedom
Multiple R-squared: 0.4207, Adjusted R-squared: 0.4189
F-statistic: 233.5 on 7 and 2251 DF, p-value: < 2.2e-16
plot(full_model)




reduced_model = step(full_model, direction ="both")
Start: AIC=3869.65
confidence ~ query_bp * basefrequency_sd * N_samples
Df Sum of Sq RSS AIC
<none> 12439 3869.7
- query_bp:basefrequency_sd:N_samples 1 14.297 12453 3870.2
reduced_model
Call:
lm(formula = confidence ~ query_bp * basefrequency_sd * N_samples,
data = lm_data)
Coefficients:
(Intercept) query_bp basefrequency_sd
4.79835 0.07638 -0.75108
N_samples query_bp:basefrequency_sd query_bp:N_samples
1.69980 -0.04625 -0.11011
basefrequency_sd:N_samples query_bp:basefrequency_sd:N_samples
-0.25966 -0.38270
summary(reduced_model)
Call:
lm(formula = confidence ~ query_bp * basefrequency_sd * N_samples,
data = lm_data)
Residuals:
Min 1Q Median 3Q Max
-17.3636 -0.9435 0.4652 1.4977 5.3559
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 4.79835 0.05705 84.110 < 2e-16 ***
query_bp 0.07638 0.07636 1.000 0.3173
basefrequency_sd -0.75108 0.10965 -6.850 9.49e-12 ***
N_samples 1.69980 0.06023 28.223 < 2e-16 ***
query_bp:basefrequency_sd -0.04625 0.19985 -0.231 0.8170
query_bp:N_samples -0.11011 0.08252 -1.334 0.1822
basefrequency_sd:N_samples -0.25966 0.13175 -1.971 0.0489 *
query_bp:basefrequency_sd:N_samples -0.38270 0.23793 -1.608 0.1079
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 2.351 on 2251 degrees of freedom
Multiple R-squared: 0.4207, Adjusted R-squared: 0.4189
F-statistic: 233.5 on 7 and 2251 DF, p-value: < 2.2e-16
plot(reduced_model)




Skmer
For skmer, we left each sample out, built a reference and then
queried that sample. We have several files in which reference samples
are ordered by their distance to the query, we here we will evaluate
whether the closest sample is from the correct species or genus.
Because it is not clear how skmer behaves for different levels of
coverage, we repeated this for several input sizes (in number of
basepairs) as query, but always used the maximum input dize available
(up to 200Mb) for references.
Let’s make a function that extracts these results as a table.
samp_labels = results %>% select(sample_id,actual_labels) %>% distinct()
extract_skmer_results = function(file_path) {
# Read only the first 2 lines of the file
file_lines <- readLines(file_path, n = 2)
# Extract sample_ID, basepairs from the first line
sample_info <- str_match(file_lines[1], "\\s*(.*?)@(\\d+K)")[, 2:3]
sample_ID <- sample_info[1]
basepairs <- sample_info[2]
# Extract reference_sample_ID, distance from the second line
reference_info <- str_match(file_lines[2], "\\s*(.*?)@.*\\s+(\\d+\\.\\d+)")[, 2:3]
reference_sample_ID <- reference_info[1]
distance <- as.numeric(reference_info[2])
# Create a tibble
tibble(
sample_id = sample_ID,
query_bp = basepairs,
closest_reference_sample_id = reference_sample_ID,
closest_distance = distance
)
}
Now we will apply this function to all skmer output files.
plan(multisession(workers = 12))
skmer_results_df = furrr::future_map_dfr(
list.files('Malpighiaceae+Chrysobalanaceae/skmer/skmer_xval_results/', full.names = T),
~ extract_skmer_results(.x)
) %>%
left_join(samp_labels, by = 'sample_id') %>%
left_join(
samp_labels %>% select(
closest_reference_sample_id = 'sample_id',
predicted_labels = actual_labels
),
by = 'closest_reference_sample_id'
) %>%
mutate(
query_labels = str_remove(actual_labels, ";*low_quality:True;*") %>% str_split(';'),
predicted_list = str_split(predicted_labels, ';')
) %>%
rowwise() %>%
mutate(
family_correct = query_labels[str_detect(query_labels, 'family')] %in% predicted_list,
genus_correct = query_labels[str_detect(query_labels, 'genus')] %in% predicted_list,
species_correct = ifelse(any(str_detect(
query_labels, 'species'
)),
query_labels[str_detect(query_labels, 'species')] %in% predicted_list,
NA),
family_incorrect = any(!(predicted_list[str_detect(predicted_list, 'family')] %in% query_labels[str_detect(query_labels, 'family')])),
genus_incorrect = any(!(predicted_list[str_detect(predicted_list, 'genus')] %in% query_labels[str_detect(query_labels, 'genus')])),
species_incorrect = ifelse(any(str_detect(
query_labels, 'species'
)),
any(!(
predicted_list[str_detect(predicted_list, 'species')] %in% query_labels[str_detect(query_labels, 'species')]
)),
NA)
)
plan(sequential)
skmer_results_df
Now let’s summarize and plot by genus:
skmer_summary_genus = summarize_results(skmer_results_df,'genus')
p_skmer_genus = plot_area(skmer_summary_genus, 'Skmer genus', relative = TRUE)
Scale for y is already present.
Adding another scale for y, which will replace the existing scale.
p_skmer_genus

Now by species. In Skmer, there is no inconclusive result: if there
is no correct species prediction, it means that a sample was predicted
in the wrong genus and therefore it is incorrect
skmer_summary_species = summarize_results(skmer_results_df,'species') %>%
mutate(result = ifelse(result == 'correct', 'correct','incorrect')) %>%
group_by(query_bp,result) %>%
summarise_all(sum)
p_skmer_species = plot_area(skmer_summary_species, 'Skmer species', relative = TRUE)
Scale for y is already present.
Adding another scale for y, which will replace the existing scale.
p_skmer_species

And now by family:
skmer_summary_family = summarize_results(skmer_results_df,'family')
skmer_summary_family
p_skmer_family = plot_area(skmer_summary_family, 'Skmer family', relative = TRUE)
Scale for y is already present.
Adding another scale for y, which will replace the existing scale.
p_skmer_family

Traditional barcodes
BLAST single gene
Let’s now read the traditional barcode BLAST results and summarize
them in the same way as skmer and varKoder. Let’s start by defining a
fuction that reads the data so we can summarize it using the previously
defined functions.
read_traditional_barcodes = function(bp) {
input_file = paste0(
'Malpighiaceae+Chrysobalanaceae/traditional_barcodes/2_blast_phylogeny_result/Genus/',
bp,
'M_blast_phylo_sum_sp.tsv'
)
barcode_res = read_delim(input_file) %>%
pivot_longer(-sp, names_to = 'marker', values_to = 'closest_reference_sample_id') %>%
rename(sample_id = 'sp') %>%
mutate(
sample_id = str_remove_all(sample_id, '@.+'),
closest_reference_sample_id = str_remove_all(closest_reference_sample_id, '@.+'),
predicted_labels = samp_labels$actual_labels[match(closest_reference_sample_id, samp_labels$sample_id)],
actual_labels = samp_labels$actual_labels[match(sample_id, samp_labels$sample_id)]
) %>%
filter(marker != 'Concatenated_phylogeny') %>%
mutate(
query_labels = str_remove(actual_labels, ";*low_quality:True;*") %>% str_split(';'),
predicted_list = str_split(predicted_labels, ';')
) %>%
rowwise() %>%
mutate(
family_correct = query_labels[str_detect(query_labels, 'family')] %in% predicted_list,
genus_correct = query_labels[str_detect(query_labels, 'genus')] %in% predicted_list,
species_correct = ifelse(any(str_detect(
query_labels, 'species'
)),
query_labels[str_detect(query_labels, 'species')] %in% predicted_list,
NA),
family_incorrect = any(!(predicted_list[str_detect(predicted_list, 'family')] %in% query_labels[str_detect(query_labels, 'family')])),
genus_incorrect = any(!(predicted_list[str_detect(predicted_list, 'genus')] %in% query_labels[str_detect(query_labels, 'genus')])),
species_incorrect = ifelse(any(str_detect(
query_labels, 'species'
)),
any(!(
predicted_list[str_detect(predicted_list, 'species')] %in% query_labels[str_detect(query_labels, 'species')]
)),
NA)
) %>%
mutate_at(vars(ends_with("_correct"), ends_with("_incorrect")),
~ ifelse(is.na(predicted_labels) & !is.na(.), FALSE, .)) %>%
mutate(query_bp = bp * 1e3)
return(barcode_res)
}
Now we can apply this function to all of our results:
results_barcodes = purrr::map_dfr(c(10,20,50,100,200),read_traditional_barcodes)
Rows: 288 Columns: 7── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr (7): sp, matK, rbcL, ndhF, trnL-F, ITS, Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Rows: 285 Columns: 7── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr (7): sp, matK, rbcL, ndhF, trnL-F, ITS, Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Rows: 267 Columns: 7── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr (7): sp, matK, rbcL, ndhF, trnL-F, ITS, Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Rows: 200 Columns: 7── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr (7): sp, matK, rbcL, ndhF, trnL-F, ITS, Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Rows: 166 Columns: 7── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr (7): sp, matK, rbcL, ndhF, trnL-F, ITS, Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
results_barcodes
Now let’s summarise for each marker separately:
barcode_summary_family = split(results_barcodes,results_barcodes$marker) %>%
purrr::map_dfr(~summarize_results(.x,'family'),.id='marker')
barcode_summary_family
barcode_summary_genus = split(results_barcodes,results_barcodes$marker) %>%
purrr::map_dfr(~summarize_results(.x,'genus'),.id='marker')
barcode_summary_genus
barcode_summary_species = split(results_barcodes,results_barcodes$marker) %>%
purrr::map_dfr(~summarize_results(.x,'species'),.id='marker')
barcode_summary_species
Now let’s plot, making separate plots for each marker:
Species:
p_barcode_species = barcode_summary_species %>%
split(barcode_summary_species$marker) %>%
purrr::map(~plot_area(.x,paste0(unique(.x$marker),' species'), relative = TRUE))
Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Scale for y is already present.
Adding another scale for y, which will replace the existing scale.
p_barcode_species
$ITS
$matK
$ndhF
$rbcL
$`trnL-F`





Genera:
p_barcode_genus = barcode_summary_genus %>%
split(barcode_summary_genus$marker) %>%
purrr::map(~plot_area(.x,paste0(unique(.x$marker),' genus'), relative = TRUE))
Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Scale for y is already present.
Adding another scale for y, which will replace the existing scale.
p_barcode_genus
$ITS
$matK
$ndhF
$rbcL
$`trnL-F`





Family:
p_barcode_family = barcode_summary_family %>%
split(barcode_summary_family$marker) %>%
purrr::map(~plot_area(.x,paste0(unique(.x$marker),' family'), relative = TRUE))
Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Scale for y is already present.
Adding another scale for y, which will replace the existing scale.
p_barcode_family
$ITS
$matK
$ndhF
$rbcL
$`trnL-F`





Concatenated tree
Now we will do the same for concatenated tree. Let’s start by
defining a function to gather results. We will consider a result as
correct if the majority of the sister taxon to a tip has the same
label.
CONTINUE AFTER WE GET THE FINAL DATASET
Now let’s apply this function
results_concat_barcodes = purrr::map_dfr(c(10,20,50,100,200),read_concatenated_tree_results)
results_concat_barcodes
Let’s summarize results and plot for genus, species and family
accuracy
concat_summary_species = summarize_results(results_concat_barcodes,'genus')
p_concat_species = plot_area(concat_summary_species, relative = TRUE,title = 'Concatenated barcodes species')
Scale for y is already present.
Adding another scale for y, which will replace the existing scale.
p_concat_species

Direct comparison
Now let’s compare methods side by side. For genus level:
reformat_graphs = function(x){
x +
theme(axis.title.x = element_blank(),
axis.text.x = element_blank(),
axis.ticks.x = element_blank()) +
labs(title = sub(" (genus|species|family)$", "", x$labels$title))
}
p = patchwork::wrap_plots(reformat_graphs(p_genus),
reformat_graphs(p_skmer_genus),
reformat_graphs(p_barcode_genus$ITS),
p_barcode_genus$rbcL +labs(title = sub(" genus$", "", p_barcode_genus$rbcL$labels$title)),
ncol = 1) +
plot_annotation(title = 'Genus-level accuracy')
p
ggsave('images_manuscript/fig3_genus_accuracy.pdf', width=4.5,height = 10)
ggsave('images_manuscript/fig3_genus_accuracy.png', width=4.5,height = 10,dpi=1200)
Now for species level:
p = patchwork::wrap_plots(reformat_graphs(p_species),
reformat_graphs(p_skmer_species),
reformat_graphs(p_barcode_species$ITS),
p_barcode_species$rbcL + labs(title = sub(" species$", "", p_barcode_species$rbcL$labels$title)),
ncol = 1) +
plot_annotation(title = 'Species-level accuracy')
p
ggsave('images_manuscript/fig3_species_accuracy.pdf', width=4.5,height = 10)
ggsave('images_manuscript/fig3_species_accuracy.png', width=4.5,height = 10,dpi=1200)
Now for species level:
p = patchwork::wrap_plots(reformat_graphs(p_family),
reformat_graphs(p_skmer_family),
reformat_graphs(p_barcode_family$ITS),
p_barcode_family$rbcL + labs(title = sub(" family$", "", p_barcode_family$rbcL$labels$title)),
ncol = 1) +
plot_annotation(title = 'family-level accuracy')
p
ggsave('images_manuscript/fig3_family_accuracy.pdf', width=4.5,height = 10)
ggsave('images_manuscript/fig3_family_accuracy.png', width=4.5,height = 10,dpi=1200)
SRA
Finally, let’s summarize results for the whole SRA dataset. In this
case, we only have varKoder since Skmer cannot finish and traditional
barcodes are inapplicable.
varKoder_SRA_results = read_csv('all_SRA/varkoder_query_results/predictions.csv') %>%
select(-1) %>%
filter(str_detect(query_basepairs,'^0+[125]0+K$')) %>% #we will ignore queries that are not standardized sizes
rename(query_bp = query_basepairs) %>%
mutate(quality_included = T)
plan(sequential)
SRA_taxlabels = str_remove(varKoder_SRA_results$actual_labels,";*low_quality:True;*") %>% str_split(';') %>% unlist %>% unique
varKoder_SRA_results = varKoder_SRA_results %>%
mutate(query_labels = str_remove(actual_labels,";*low_quality:True;*") %>% str_split(';') %>% unlist,
predicted_list = str_split(predicted_labels,';')
) %>%
rowwise() %>%
mutate(family_correct = query_labels %in% predicted_list,
family_incorrect = ifelse(is.na(predicted_labels),FALSE,any(!(predicted_list %in% query_labels)))) %>%
select(matches("^[^0-9]"))
varKoder_SRA_results
Now let’s summarize and plot:
SRA_summary_family = summarize_results(varKoder_SRA_results,'family')
SRA_summary_family
N_samp = SRA_summary_family %>%
group_by(query_bp) %>%
summarise(N = sum(N))
p_SRA_family = plot_area(SRA_summary_family, 'varKoder SRA family', relative = TRUE)
p_SRA_family
Let’s now do the SRA plot, but splitting by kingdom. First, we need
to retrieve kingdom information:
p_SRA_families = read_csv('all_SRA/runs_to_download_data.csv') %>%
select(sample_id = Run, Kingdom) %>%
right_join(varKoder_SRA_results) %>%
split(.$Kingdom) %>%
purrr::map(summarize_results,
level='family') %>%
purrr::imap(~plot_area(.x,.y,relative=TRUE) + coord_cartesian(xlim=c(500,10000)*1000,expand = FALSE))
p_SRA_families
Now let’s join to create a plot for publication:
remove_y_axis_and_scale = function(x){
x +
theme(axis.title.y = element_blank(),
axis.text.y = element_blank(),
axis.ticks.y = element_blank(),
legend.position='none',
axis.title.x = element_blank())
}
# Modify the last plot to have the x-axis label
p_SRA_families$Fungi <- p_SRA_families$Fungi + labs(x = "Base pairs in query images")
# Combine plots
p_combined <- wrap_plots(p_SRA_families$Metazoa +
theme(legend.position = 'none',
axis.title.x = element_blank()),
remove_y_axis_and_scale(p_SRA_families$Viridiplantae),
remove_y_axis_and_scale(p_SRA_families$Fungi),
nrow = 1)
# Add title and set layout
# Create a blank ggplot object with the desired x-axis title
x_title_plot <- ggplot() +
theme_void() +
labs(x = "Base pairs in query images") +
theme(plot.margin = margin(0, 0, 0, 0),
axis.title.x = element_text(size = 10, hjust = 0.5))
p = wrap_plots(p_combined, x_title_plot,ncol=1,heights = c(0.95,0.05))
print(p)
ggsave('images_manuscript/fig3_SRA_accuracy.pdf', width=4.5,height = 4)
ggsave('images_manuscript/fig3_SRA_accuracy.png', width=4.5,height = 4,dpi = 1200)
Generating numbers for publication
Here we just query our results to get a few figures that we report in
the paper.
Total number of samples used in cross-validation:
dim(samp_labels)
Number of Stigmaphyllon samples with each kind of error for
varkoder:
summary_species
Number of Stigmaphyllon samples with each kind of error for
skmer:
skmer_summary_species
varKoder accuracy for genera:
summary_genus
varKoder accuracy for family:
summary_family
Skmer accuracy for genera:
skmer_summary_genus
Skmer accuracy for family:
skmer_summary_family
Number of samples available for each genus and data amount
results %>%
mutate(genus = str_extract(actual_labels,"(?<=genus:)[^;]+")) %>%
group_by(query_bp) %>%
summarize(N=n()) %>%
complete()
Plot number of samples for supplementary material.
n_samples_genera = results %>%
mutate(taxon = str_extract(actual_labels,"(?<=genus:)[^;]+")) %>%
group_by(taxon, query_bp) %>%
summarize(N=n()) %>%
ungroup() %>%
complete(taxon, query_bp, fill = list(N=0))
n_samples_genera
n_samples_species = results %>%
mutate(taxon = str_extract(actual_labels,"(?<=species:)[^;]+")) %>%
filter(!is.na(taxon)) %>%
group_by(taxon, query_bp) %>%
summarize(N=n()) %>%
ungroup() %>%
complete(taxon, query_bp, fill = list(N=0))
n_samples_species
n_samples_SRA = varKoder_SRA_results %>%
mutate(taxon = as.character(actual_labels)) %>%
group_by(taxon, query_bp) %>%
summarize(N=n()) %>%
ungroup() %>%
complete(taxon, query_bp, fill = list(N=0))
n_samples_SRA
plot_Nsamples_area = function(df, title){
df = df %>%
mutate(query_bp = parse_number(query_bp) *1000)
n_levels <- length(unique(df$taxon))
viridis_colors <- viridis::turbo(n_levels)
half_n <- ceiling(n_levels / 2)
reordered_colors <- c(rbind(viridis_colors[1:half_n], viridis_colors[(half_n + 1):n_levels]))
ggplot(df, aes(x=query_bp,y=N,fill=taxon, color = taxon, group = taxon)) +
geom_area(position= position_stack()) +
#geom_line(position='stack') +
scale_fill_manual(values = reordered_colors,
aesthetics = c('colour','fill'),
guide = 'none') +
scale_x_log10(labels = scales::label_number(scale_cut = scales::cut_si('bp')),
breaks = 1000*parse_number(unique(n_samples_genera$query_bp)),
limits = 1000*range(parse_number(unique(n_samples_genera$query_bp)))) +
scale_y_continuous(n.breaks = 10, minor_breaks = waiver()) +
ggtitle(title) +
ylab('Number of samples') +
xlab('Base pairs in query images') +
theme_few() +
theme(axis.text.x = element_text(hjust=1,angle=45),
panel.background = element_rect(fill = NA),
panel.grid.major.y = element_line(colour = gray(0.5)),
panel.grid.minor.y = element_line(colour = gray(0.6),linetype = 2),
panel.ontop = TRUE)
}
N_species = plot_Nsamples_area(n_samples_species,title='Stigmaphyllon Species')
N_genera = plot_Nsamples_area(n_samples_genera,title='Maplighiaceae and Chrysobalanaceae Genera')
N_families = plot_Nsamples_area(n_samples_SRA,title='SRA familes')
cowplot::plot_grid(N_genera,N_species,N_families, nrow = 1)
Total number of SRA samples. Validation:
read_csv('varKoder/all_SRA/varkoder_trained_model_ML/input_data.csv')[-1] %>%
group_by(is_valid) %>%
summarise(N = n())
LS0tCnRpdGxlOiAiVmFyS29kZXIsIFNrbWVyIGFuZCB0cmFkaXRpb25hbCBiYXJjb2RpbmcgQ3Jvc3MtdmFsaWRhdGlvbiIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKVG8gY29tcGFyZSB0aGUgcGVyZm9ybWFuY2Ugb2YgdmFyS29kZSB0byBbU2ttZXJdKGh0dHBzOi8vZ2l0aHViLmNvbS9zaGFoYWItc2FybWFzaGdoaS9Ta21lciksIHdlIHdpbGwgdXNlIGxlYXZlLW9uZS1vdXQgY3Jvc3MgdmFsaWRhdGlvbjogd2UgcmVtb3ZlIG9uZSBzYW1wbGUgZnJvbSB0aGUgZGF0YXNldCwgdHJhaW4gYSB2YXJLb2RlIG1vZGVsIG9yIG1ha2UgYSBza21lciByZWZlcmVuY2Ugd2l0aCB0aGUgcmVtYWluaW5nIHNhbXBsZXMsIGFuZCB0aGVuIHVzZSB0aGUgc2FtcGxlIGxlZnQgb3V0IGFzIHF1ZXJ5LiBXZSB0aGVuIHJlY29yZCB3aGV0aGVyIG9yIG5vdCB3ZSBjb3JyZWN0bHkgaWRlbnRpZnkgdGhpcyBzYW1wbGUgaW4gdmFyS29kZXIsIGFuZCB3aGV0aGVyIG9yIG5vdCB0aGUgY2xvc2VzdCBzYW1wbGUgd2l0aCBTa21lciBoYXMgdGhlIHNhbWUgaWRlbnRpZmljYXRpb24uIAoKRm9yIHRyYWRpdGlvbmFsIGJhcmNvZGVzLCB3ZSBhc3NlbWJsZWQgdGhlIGdlbm9tZSBvZiBlYWNoIHNhbXBsZSwgYW5kIHRoZW4gdXNlZCBCTEFTVCB0byBzZWFyY2ggZm9yIGVhY2ggb2YgdGhlIHRyYWRpdGlvbmFsIGJhcmNvZGUgZ2VuZXMuIFdlIHJlY29yZGVkIGlmIHdlIGNvdWxkIGZpbmQgdGhpcyBnZW5lIGluIHRoZSBhc3NlbWJseSwgY29kaW5nIGFzIG1pc3NpbmcgZGF0YSBpZiB3ZSBjb3VsZCBub3QuIFdlIHRoZW4gcmVjb3JkZWQgd2hldGhlciB0aGUgYmVzdCBCTEFTVCBoaXQgZm9yIGEgc2FtcGxlIHdhcyB0aGUgY29ycmVjdCBzcGVjaWVzLgoKYGBge3J9CnJtKGxpc3Q9bHMoKSkKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoZnV0dXJlKQpsaWJyYXJ5KGdndGhlbWVzKQpsaWJyYXJ5KHBhdGNod29yaykKbGlicmFyeShjb3dwbG90KQpsaWJyYXJ5KHBhdGNod29yaykKbGlicmFyeShwaHl0b29scykKbGlicmFyeShhcGUpCmBgYAojIFZhcktvZGVyCkZvciBWYXJLb2Rlciwgd2UgdXNlZCBsZWF2ZS1vbmUtb3V0IGNyb3NzLXZhbGlkYXRpb24gdG8gdGVzdCB0aGUgYWNjdXJhY3kgZm9yIGZhbWlseSwgZ2VuZXJhLCBzcGVjaWVzIGluIHRoZSBqb2ludCBNYWxwaWdoaWFjZWFlLUNocnlzb2JhbGFuYWNlYWUgZGF0YXNldC4gV2UgdXNlZCBhcyBpbnB1dCBkYXRhIHZhcktvZGVzIHByb2R1Y2VkIGZyb20ga21lcnMgb2Ygc2l6ZSA3IGFuZCA1MDBLYnAgdG8gMjAwTWJwIG9mIGRhdGEsIG9yIGFsbCBvZiB0aGUgZGF0YSBhdmFpbGFibGUgaWYgbGVzcyB0aGFuIDIwMCBNYnAuIEZvciBlYWNoIHNhbXBsZSwgd2UgYnVpbHQgYSBtb2RlbCB1c2luZyBhcyBpbnB1dCBkYXRhIGZyb20gYWxsIG90aGVyIHNhbXBsZXMuIFRoZW4gd2UgcXVlcmllZCB0aGUgc2FtcGxlIGxlZnQgb3V0LCB1c2luZyBhcyBpbnB1dCB0aGUgaW1hZ2VzIGdlbmVyYXRlZCBmcm9tIDUwMEtiIHRvIHRoZSB0b3RhbCBkYXRhIGF2YWlsYWJsZS4gTm93IHdlIHdpbGwgc3VtbWFyaXplIHRoZSByZXN1bHRzLgoKCiMjIEFjY3VyYWN5IHZzIGRhdGEgYW1vdW50IGFuZCB0YXhvbm9taWMgbGV2ZWxzCgpJbiB0aGlzIHRlc3QsIHdlIHVzZWQgdmFyS29kZXIgW3YwLjYuMF0oaHR0cHM6Ly9naXRodWIuY29tL2JydW5vYXNtL3ZhcktvZGVyL3JlbGVhc2VzL3RhZy92LjAuNi4wKS4gTGV0J3MgcHJvY2VzcyB0aGUgcmVzdWx0cy4KCmBgYHtyfQpyZWFkX2FuZF9wcm9jZXNzX3h2YWwgPSBmdW5jdGlvbihpbmZvbGRlcil7CiAgcGxhbihtdWx0aXNlc3Npb24od29ya2VycyA9IDEyKSkKdmFya29kZXJfcmVzdWx0cyA9IGxpc3QuZmlsZXMoaW5mb2xkZXIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ3ByZWRpY3Rpb25zLmNzdicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVjdXJzaXZlPVQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnVsbC5uYW1lcyA9IFQpICU+JQogIGZ1cnJyOjpmdXR1cmVfbWFwX2Rmcih+cmVhZF9jc3YoLngpICU+JSBtdXRhdGUoc2FtcGxlX2lkID0gYXMuY2hhcmFjdGVyKHNhbXBsZV9pZCkpKSAlPiUgCiAgc2VsZWN0KC0xKSAlPiUKICBmaWx0ZXIoc3RyX2RldGVjdChxdWVyeV9iYXNlcGFpcnMsJ14wK1sxMjVdMCtLJCcpKSAlPiUgI3dlIHdpbGwgaWdub3JlIHF1ZXJpZXMgdGhhdCBhcmUgbm90IHN0YW5kYXJkaXplZCBzaXplcwogIHJlbmFtZShxdWVyeV9icCA9IHF1ZXJ5X2Jhc2VwYWlycykgJT4lCiAgbXV0YXRlKHF1YWxpdHlfaW5jbHVkZWQgPSBUKQpwbGFuKHNlcXVlbnRpYWwpCgphbGxfdGF4bGFiZWxzID0gc3RyX3JlbW92ZSh2YXJrb2Rlcl9yZXN1bHRzJGFjdHVhbF9sYWJlbHMsIjsqbG93X3F1YWxpdHk6VHJ1ZTsqIikgJT4lIHN0cl9zcGxpdCgnOycpICU+JSB1bmxpc3QgJT4lIHVuaXF1ZQoKdmFya29kZXJfcmVzdWx0cyA9IHZhcmtvZGVyX3Jlc3VsdHMgJT4lCiAgbXV0YXRlKHF1ZXJ5X2xhYmVscyA9IHN0cl9yZW1vdmUoYWN0dWFsX2xhYmVscywiOypsb3dfcXVhbGl0eTpUcnVlOyoiKSAlPiUgc3RyX3NwbGl0KCc7JyksCiAgICAgICAgIHByZWRpY3RlZF9saXN0ID0gc3RyX3NwbGl0KHByZWRpY3RlZF9sYWJlbHMsJzsnKQogICAgICAgICApICU+JQogIHJvd3dpc2UoKSAlPiUKICBtdXRhdGUoZmFtaWx5X2NvcnJlY3QgPSBxdWVyeV9sYWJlbHNbc3RyX2RldGVjdChxdWVyeV9sYWJlbHMsJ2ZhbWlseScpXSAlaW4lIHByZWRpY3RlZF9saXN0LAogICAgICAgICBnZW51c19jb3JyZWN0ID0gcXVlcnlfbGFiZWxzW3N0cl9kZXRlY3QocXVlcnlfbGFiZWxzLCdnZW51cycpXSAlaW4lIHByZWRpY3RlZF9saXN0LAogICAgICAgICBzcGVjaWVzX2NvcnJlY3QgPSBpZmVsc2UoYW55KHN0cl9kZXRlY3QocXVlcnlfbGFiZWxzLCdzcGVjaWVzJykpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcXVlcnlfbGFiZWxzW3N0cl9kZXRlY3QocXVlcnlfbGFiZWxzLCdzcGVjaWVzJyldICVpbiUgcHJlZGljdGVkX2xpc3QsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBOQQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgZmFtaWx5X2luY29ycmVjdCA9IGFueSghKHByZWRpY3RlZF9saXN0W3N0cl9kZXRlY3QocHJlZGljdGVkX2xpc3QsJ2ZhbWlseScpXSAlaW4lIHF1ZXJ5X2xhYmVsc1tzdHJfZGV0ZWN0KHF1ZXJ5X2xhYmVscywnZmFtaWx5JyldKSksCiAgICAgICAgIGdlbnVzX2luY29ycmVjdCA9IGFueSghKHByZWRpY3RlZF9saXN0W3N0cl9kZXRlY3QocHJlZGljdGVkX2xpc3QsJ2dlbnVzJyldICVpbiUgcXVlcnlfbGFiZWxzW3N0cl9kZXRlY3QocXVlcnlfbGFiZWxzLCdnZW51cycpXSkpLAogICAgICAgICBzcGVjaWVzX2luY29ycmVjdCA9IGlmZWxzZShhbnkoc3RyX2RldGVjdChxdWVyeV9sYWJlbHMsJ3NwZWNpZXMnKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbnkoIShwcmVkaWN0ZWRfbGlzdFtzdHJfZGV0ZWN0KHByZWRpY3RlZF9saXN0LCdzcGVjaWVzJyldICVpbiUgcXVlcnlfbGFiZWxzW3N0cl9kZXRlY3QocXVlcnlfbGFiZWxzLCdzcGVjaWVzJyldKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBOQQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAKICAgICAgICAgKQoKcmV0dXJuKHZhcmtvZGVyX3Jlc3VsdHMpCn0KYGBgCgoKYGBge3J9CnN1bW1hcml6ZV9yZXN1bHRzID0gZnVuY3Rpb24ocmVzLGxldmVsKXsKICByZXMgPSByZXMgJT4lCiAgICB1bmdyb3VwKCkgJT4lCiAgICBtdXRhdGUobG93X3F1YWxpdHkgPSBzdHJfZGV0ZWN0KGFjdHVhbF9sYWJlbHMsImxvd19xdWFsaXR5OlRydWUiKSwKICAgICAgICAgICByZXN1bHQgPSBhcy5jaGFyYWN0ZXIoaWZlbHNlKHJlc1ssc3RyX2MobGV2ZWwsJ2NvcnJlY3QnLHNlcD0nXycpXSAmICFyZXNbLHN0cl9jKGxldmVsLCdpbmNvcnJlY3QnLHNlcD0nXycpXSwgJ2NvcnJlY3QnLAogICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UocmVzWyxzdHJfYyhsZXZlbCwnY29ycmVjdCcsc2VwPSdfJyldICYgcmVzWyxzdHJfYyhsZXZlbCwnaW5jb3JyZWN0JyxzZXA9J18nKV0sICdhbWJpZ3VvdXMnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKCFyZXNbLHN0cl9jKGxldmVsLCdjb3JyZWN0JyxzZXA9J18nKV0gICYgcmVzWyxzdHJfYyhsZXZlbCwnaW5jb3JyZWN0JyxzZXA9J18nKV0sICdpbmNvcnJlY3QnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ2luY29uY2x1c2l2ZScKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkpKSkKICAgICAgICAgICApICU+JQogICAgZmlsdGVyKCFpcy5uYShyZXN1bHQpKSAlPiUKICAgIGdyb3VwX2J5KHF1ZXJ5X2JwLHJlc3VsdCkgJT4lCiAgICBzdW1tYXJpc2UoTj1uKCksIC5ncm91cHMgPSAnZHJvcCcpICU+JQogICAgZ3JvdXBfYnkocXVlcnlfYnApICU+JQogICAgbXV0YXRlKHA9IE4vc3VtKE4pKSAlPiUKICAgIG11dGF0ZShxdWVyeV9icCA9IGFzLmludGVnZXIoc3RyX3JlbW92ZShxdWVyeV9icCwnSycpKSoxMDAwKSAlPiUKICAgIHVuZ3JvdXAoKSAlPiUKICAgIG11dGF0ZShxdWVyeV9icCA9IGFzLmZhY3RvcihxdWVyeV9icCkpICU+JQogICAgY29tcGxldGUocXVlcnlfYnAscmVzdWx0LCBmaWxsID0gbGlzdChwID0gMCwgTiA9IDApKSAlPiUKICAgIG11dGF0ZShxdWVyeV9icCA9IGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKHF1ZXJ5X2JwKSkpICU+JQogICAgdW5ncm91cCgpCiAgICAKICByZXR1cm4ocmVzKQp9CmBgYAoKCmBgYHtyfQpwbG90X2FyZWEgPSBmdW5jdGlvbihzdW1fZGYsIHRpdGxlLCByZWxhdGl2ZSA9IEZBTFNFLCBncmlkID0gVFJVRSl7CiAgYnJlYWtzID0gYyg1MDAwMDAsCiAgICAgICAgICAgICAxMDAwMDAwLAogICAgICAgICAgICAgMjAwMDAwMCwKICAgICAgICAgICAgIDUwMDAwMDAsCiAgICAgICAgICAgICAxMDAwMDAwMCwKICAgICAgICAgICAgIDIwMDAwMDAwLAogICAgICAgICAgICAgNTAwMDAwMDAsCiAgICAgICAgICAgICAxMDAwMDAwMDAsCiAgICAgICAgICAgICAyMDAwMDAwMDAKICAgICAgICAgICAgICkKICB4bGltaXRzID0gcmFuZ2UoYnJlYWtzKQogIAogIHN1bV9kZiA9IHN1bV9kZiAlPiUKICAgIG11dGF0ZShyZXN1bHQgPSBmYWN0b3IocmVzdWx0LG9yZGVyZWQgPSBULCBsZXZlbHMgPSBjKCdjb3JyZWN0JywnYW1iaWd1b3VzJywnaW5jb25jbHVzaXZlJywnaW5jb3JyZWN0JykpKSAKICBpZiAocmVsYXRpdmUpewogICAgeWxpbWl0cyA9IGMoMCwxKQogIH0gZWxzZSB7CiAgICB5bGltaXRzID0gYygwLHN1bV9kZiAlPiUgZ3JvdXBfYnkocXVlcnlfYnApICU+JSBzdW1tYXJpemUoTj1zdW0oTikpICU+JSBwdWxsKE4pICU+JSBtYXgpCiAgfQogIAogIAogICMgR2V0IGNvbG9ycyBmcm9tIGEgQ29sb3IgQnJld2VyIHBhbGV0dGUKICBicmV3ZXJfY29sb3JzIDwtIFJDb2xvckJyZXdlcjo6YnJld2VyLnBhbCg0LCAiQWNjZW50IikKICAKICBpZiAocmVsYXRpdmUpIHsKICAgIHAxID0gZ2dwbG90KHN1bV9kZiwgYWVzKHg9cXVlcnlfYnAseT1wLGZpbGw9cmVzdWx0KSkgKwogICAgZ2VvbV9hcmVhKHBvc2l0aW9uPSdzdGFjaycpICsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHNldE5hbWVzKGJyZXdlcl9jb2xvcnMsIGMoImNvcnJlY3QiLCAiYW1iaWd1b3VzIiwgImluY29uY2x1c2l2ZSIsICJpbmNvcnJlY3QiKSkpICsKICAgIHNjYWxlX2FscGhhX21hbnVhbCh2YWx1ZXM9YygwLjUsMSkpICsKICAgIHNjYWxlX3hfbG9nMTAobGFiZWxzID0gc2NhbGVzOjpsYWJlbF9udW1iZXIoc2NhbGVfY3V0ID0gc2NhbGVzOjpjdXRfc2koJ2JwJykpLGJyZWFrcyA9IGJyZWFrcykgICsKICAgIHNjYWxlX3lfY29udGludW91cygpICsKICAgIGdndGl0bGUodGl0bGUpICsKICAgIHlsYWIoJ0ZyYWN0aW9uIG9mIHNhbXBsZXMnKSArCiAgICB4bGFiKCdCYXNlIHBhaXJzIGluIHF1ZXJ5IGltYWdlcycpICsKICAgIHRoZW1lX2ZldygpICsKICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGhqdXN0PTEsYW5nbGU9NDUpKQogIH0gZWxzZSB7CiAgICAgIHAxID0gZ2dwbG90KHN1bV9kZiwgYWVzKHg9cXVlcnlfYnAseT1OLGZpbGw9cmVzdWx0KSkgKwogICAgZ2VvbV9hcmVhKHBvc2l0aW9uPSdzdGFjaycpICsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHNldE5hbWVzKGJyZXdlcl9jb2xvcnMsIGMoImNvcnJlY3QiLCAiYW1iaWd1b3VzIiwgImluY29uY2x1c2l2ZSIsICJpbmNvcnJlY3QiKSkpICsKICAgIHNjYWxlX2FscGhhX21hbnVhbCh2YWx1ZXM9YygwLjUsMSkpICsKICAgIHNjYWxlX3hfbG9nMTAobGFiZWxzID0gc2NhbGVzOjpsYWJlbF9udW1iZXIoc2NhbGVfY3V0ID0gc2NhbGVzOjpjdXRfc2koJ2JwJykpLGJyZWFrcyA9IGJyZWFrcykgICArCiAgICBzY2FsZV95X2NvbnRpbnVvdXMoKSArCiAgICBnZ3RpdGxlKHRpdGxlKSArCiAgICB5bGFiKCdOdW1iZXIgb2Ygc2FtcGxlcycpICsKICAgIHhsYWIoJ0Jhc2UgcGFpcnMgaW4gcXVlcnkgaW1hZ2VzJykgKwogICAgdGhlbWVfZmV3KCkgKwogICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoaGp1c3Q9MSxhbmdsZT00NSkpCiAgfQogIAogIGlmIChncmlkKXsKICAgIHAxID0gcDEgKwogICAgICBzY2FsZV95X2NvbnRpbnVvdXMobi5icmVha3MgPSAxMCwgbWlub3JfYnJlYWtzID0gd2FpdmVyKCkpICsKICAgICAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gTkEpLAogICAgICAgICAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2xpbmUoY29sb3VyID0gZ3JheSgwLjUpKSwKICAgICAgICAgICAgcGFuZWwuZ3JpZC5taW5vci55ID0gZWxlbWVudF9saW5lKGNvbG91ciA9IGdyYXkoMC42KSxsaW5ldHlwZSA9IDIpLAogICAgICAgICAgICBwYW5lbC5vbnRvcCA9IFRSVUUpCiAgfQogIAogIHAxID0gcDEgKyBjb29yZF9jYXJ0ZXNpYW4oeGxpbT14bGltaXRzLCB5bGltPXlsaW1pdHMsZXhwYW5kID0gRkFMU0UpCiAgCiAgcmV0dXJuKHAxKQp9CiAgCmBgYAoKCk5vdyBsZXQncyBwbG90IGdlbnVzLWxldmVsIGFjY3VyYWN5IGZvciBhIG1vZGVsIHRha2luZyBxdWFsaXR5IGxhYmVscyBpbnRvIGFjY291bnQ6CgoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQpyZXN1bHRzID0gcmVhZF9hbmRfcHJvY2Vzc194dmFsKCdNYWxwaWdoaWFjZWFlK0Nocnlzb2JhbGFuYWNlYWUvdmFyS29kZXIvdml0X3Jlc3VsdHMvJykKc3VtbWFyeV9nZW51cyA9IHN1bW1hcml6ZV9yZXN1bHRzKHJlc3VsdHMsJ2dlbnVzJykKcF9nZW51cyA9IHBsb3RfYXJlYShzdW1tYXJ5X2dlbnVzLCAndmFyS29kZXIgZ2VudXMnLCByZWxhdGl2ZSA9IFRSVUUpCnBfZ2VudXMKYGBgCk5vdyB0aGUgc2FtZSBidXQgd2l0aCBzcGVjaWVzCmBgYHtyfQpzdW1tYXJ5X3NwZWNpZXMgPSBzdW1tYXJpemVfcmVzdWx0cyhyZXN1bHRzLCdzcGVjaWVzJykKcF9zcGVjaWVzID0gcGxvdF9hcmVhKHN1bW1hcnlfc3BlY2llcywgJ3ZhcktvZGVyIHNwZWNpZXMnLCByZWxhdGl2ZSA9IFRSVUUpCnBfc3BlY2llcwpgYGAKCkZpbmFsbHksIGZhbWlseQpgYGB7cn0Kc3VtbWFyeV9mYW1pbHkgPSBzdW1tYXJpemVfcmVzdWx0cyhyZXN1bHRzLCdmYW1pbHknKQpwX2ZhbWlseSA9IHBsb3RfYXJlYShzdW1tYXJ5X2ZhbWlseSwgJ3ZhcktvZGVyIGZhbWlseScsIHJlbGF0aXZlID0gVFJVRSkKcF9mYW1pbHkKYGBgCiMjIHdoYXQgZXhwbGFpbnMgdGhlIGVycm9ycz8KCk5vdyB3ZSB3aWxsIHRyeSB0byBpZGVudGlmeSB3aGljaCBzYW1wbGVzIGZhaWxlZCBhbmQgd2h5IHRoZXkgZmFpbGVkLiBQYXJ0aWN1YXJseSwgaG93IGRvIApETkEgcXVhbGl0eSwgYW1vdW50IG9mIGRhdGEsIGFuZCB0aGUgbnVtYmVyIG9mIHNhbXBsZXMgcGVyIGNsYXNzIGltcGFjdCByZXN1bHRzPyBXZSB3aWxsIHVzZSBnZW51cy1sZXZlbCBwcmVkaWN0aW9ucyB0byB0ZXN0LgoKYGBge3J9CmdlbnVzX3ByZWRpY3Rpb25zID0gcmVzdWx0cyAlPiUKICBtdXRhdGUocHJlZGljdGVkX2dlbnVzID0gc3RyX2V4dHJhY3QocHJlZGljdGVkX2xhYmVscywgJ2dlbnVzOlteO10qJyksCiAgICAgICAgIGFjdHVhbF9nZW51cyA9IHN0cl9leHRyYWN0KGFjdHVhbF9sYWJlbHMsICdnZW51czpbXjtdKicpKSAlPiUKICBzZWxlY3QoLXN0YXJ0c193aXRoKCdmYW1pbHknKSwtc3RhcnRzX3dpdGgoJ3NwZWNpZXMnKSkgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSBzdGFydHNfd2l0aCgiZ2VudXMiKSwgbmFtZXNfdG8gPSAicHJlZGljdGVkX2xhYmVsIiwgdmFsdWVzX3RvID0gImNvbmZpZGVuY2UiKSAlPiUKICBmaWx0ZXIoYWN0dWFsX2dlbnVzID09IHByZWRpY3RlZF9sYWJlbCkgJT4lCiAgc2VsZWN0KHF1ZXJ5X2JwLCBzYW1wbGVfaWQsIGJhc2VmcmVxdWVuY3lfc2QsIGFjdHVhbF9nZW51cywgY29uZmlkZW5jZSkgJT4lCiAgbXV0YXRlKHF1ZXJ5X2JwID0gMTAwMCooc3RyX3JlbW92ZShxdWVyeV9icCwgIksiKSAlPiUgYXMuaW50ZWdlcikpCgpnZW51c19wcmVkaWN0aW9ucyA9IGdlbnVzX3ByZWRpY3Rpb25zICU+JQogIHNlbGVjdChzYW1wbGVfaWQsIGFjdHVhbF9nZW51cykgJT4lCiAgZGlzdGluY3QoKSAlPiUKICBncm91cF9ieShhY3R1YWxfZ2VudXMpICU+JQogIHN1bW1hcmlzZShOX3NhbXBsZXMgPSBuKCkpICU+JQogIHJpZ2h0X2pvaW4oZ2VudXNfcHJlZGljdGlvbnMpCgpnZW51c19wcmVkaWN0aW9ucwpgYGAKTm93IGxldCdzIG1ha2Ugc29tZSBwbG90cy4gRmlyc3QsIHdoYXQgaXMgdGhlIGVmZmVjdCBvZiBudW1iZXIgb2Ygc2FtcGxlcyBwZXIgY2xhc3MgaW4gY29uZmlkZW5jZT8KYGBge3J9CnBsb3RfZ2VudXNfTl92c19jb25mID0gZ2dwbG90KGdlbnVzX3ByZWRpY3Rpb25zLCBhZXMoeCA9IE5fc2FtcGxlcy0xLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IGNvbmZpZGVuY2UpKSArIAogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfYygpICsKICBnZW9tX2ppdHRlcihhbHBoYT0wLjMpICsgCiAgc2NhbGVfeF9sb2cxMCgpICsKICAjeWxhYignQ29uZmlkZW5jZSBpbiBjb3JyZWN0IHByZWRpY3Rpb25cbihsb2dpdCBzY2FsZSknKSArCiAgeWxhYignQ29uZmlkZW5jZSBpbiBjb3JyZWN0IHByZWRpY3Rpb24nKSArCiAgeGxhYignTnVtYmVyIG9mIHNhbXBsZXMgaW4gY29ycmVjdCBnZW51c1xuKGxvZyBzY2FsZSknKSArCiAgI3NjYWxlX3lfY29udGludW91cyh0cmFucyA9ICJsb2dpdCIsIGJyZWFrcyA9IGMoMWUtNCwwLjAwMSwwLjAxLDAuMSwwLjI1LDAuNSwwLjc1LDAuOSwwLjk5LDAuOTk5LDEtMWUtNCkpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzPWMoMCwxKSkgKwogIHRoZW1lX2ZldygpICsKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2xpbmUoY29sb3VyID0gZ3JheSgwLjgpKSkKCnBsb3RfZ2VudXNfTl92c19jb25mCmBgYAoKTm93LCB3aGF0IGlzIHRoZSBlZmZlY3Qgb2Ygc2FtcGxlIHF1YWxpdHkgaW4gY29uZmlkZW5jZT8KYGBge3J9CnBsb3RfZ2VudXNfZnJlcXNkX3ZzX2NvbmYgPSBnZ3Bsb3QoZ2VudXNfcHJlZGljdGlvbnMsIGFlcyh4ID0gYmFzZWZyZXF1ZW5jeV9zZCwgeSA9IGNvbmZpZGVuY2UpKSArIAogIGdlb21fcG9pbnQoYWxwaGE9MC4zKSArIAogIHNjYWxlX3hfbG9nMTAoKSArCiAgI3NjYWxlX3lfY29udGludW91cyh0cmFucyA9ICJsb2dpdCIsIGJyZWFrcyA9IGMoMWUtNCwwLjAwMSwwLjAxLDAuMSwwLjI1LDAuNSwwLjc1LDAuOSwwLjk5LDAuOTk5LDEtMWUtNCkpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzPWMoMCwxKSkgKwogICN5bGFiKCdDb25maWRlbmNlIGluIGNvcnJlY3QgcHJlZGljdGlvblxuKGxvZ2l0IHNjYWxlKScpICsKICB5bGFiKCdDb25maWRlbmNlIGluIGNvcnJlY3QgcHJlZGljdGlvbicpICsKICB4bGFiKCdTdGFuZGFyZCBkZXZpYXRpb24gb2YgYmFzZSBmcmVxdWVuY2llcycpICsKICB0aGVtZV9mZXcoKSArCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9saW5lKGNvbG91ciA9IGdyYXkoMC44KSkpCgpwbG90X2dlbnVzX2ZyZXFzZF92c19jb25mCmBgYAoKTm93LCB3aGF0IGlzIHRoZSBlZmZlY3Qgb2YgYW1vdW50IG9mIGRhdGEgaW4gY29uZmlkZW5jZT8KYGBge3J9CnBsb3RfZ2VudXNfYnBfdnNfY29uZiA9IGdncGxvdChnZW51c19wcmVkaWN0aW9ucywgYWVzKHggPSBxdWVyeV9icCwgeSA9IGNvbmZpZGVuY2UpKSArIAogIGdlb21faml0dGVyKGFscGhhPTAuMykgKyAKICAjc2NhbGVfeV9jb250aW51b3VzKHRyYW5zID0gImxvZ2l0IiwgYnJlYWtzID0gYygxZS00LDAuMDAxLDAuMDEsMC4xLDAuMjUsMC41LDAuNzUsMC45LDAuOTksMC45OTksMS0xZS00KSkgKwogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHM9YygwLDEpKSArCiAgI3lsYWIoJ0NvbmZpZGVuY2UgaW4gY29ycmVjdCBwcmVkaWN0aW9uXG4obG9naXQgc2NhbGUpJykgKwogIHlsYWIoJ0NvbmZpZGVuY2UgaW4gY29ycmVjdCBwcmVkaWN0aW9uJykgKwogIHhsYWIoJ0Jhc2UgcGFpcnMgaW4gcXVlcnkgaW1hZ2VzXG4obG9nIHNjYWxlKScpICsKICBzY2FsZV94X2xvZzEwKCkgKwogIHRoZW1lX2ZldygpICsKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2xpbmUoY29sb3VyID0gZ3JheSgwLjgpKSkKCnBsb3RfZ2VudXNfYnBfdnNfY29uZgpgYGAKCk5vdyBsZXQncyBzYXZlIHRoZSB0aHJlZSBvZiB0aGVtIGFzIGEgc2luZ2xlIHBsb3QgdXNpbmcgY293cGxvdC4KCmBgYHtyfQpjb21iaW5lZF9jb25mID0gcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKHBsb3RfZ2VudXNfTl92c19jb25mICsgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChzaXplPTgpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwbG90X2dlbnVzX2JwX3ZzX2NvbmYgKyB0aGVtZShheGlzLnRpdGxlLnk9ZWxlbWVudF9ibGFuaygpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT04KSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGxvdF9nZW51c19mcmVxc2RfdnNfY29uZiArIHRoZW1lKGF4aXMudGl0bGUueT1lbGVtZW50X2JsYW5rKCksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT04KSkpICsKICBwYXRjaHdvcms6OnBsb3RfYW5ub3RhdGlvbih0YWdfbGV2ZWxzID0gJ0EnKSAKCmNvbWJpbmVkX2NvbmYKCmdnc2F2ZShmaWxlbmFtZSA9ICdpbWFnZXNfbWFudXNjcmlwdC9zdXBwX2NvbmZfcHJlZGljdG9ycy5wZGYnLGRldmljZSA9ICdwZGYnLHdpZHRoID0gNyxoZWlnaHQ9Myx1bml0cyA9ICdpbicsdXNlRGluZ2JhdHM9RikKYGBgCgoKTGV0J3MgcHV0IGl0IGFsbCB0b2dldGhlciBub3cgaW4gYSBsaW5lYXIgbW9kZWw6CgpgYGB7cn0KbG1fZGF0YSA9IGdlbnVzX3ByZWRpY3Rpb25zICU+JQogIG11dGF0ZShjb25maWRlbmNlID0gaWZlbHNlKGNvbmZpZGVuY2UgPT0gMSwgY29uZmlkZW5jZS0wLjAwMDAwMDEsIGNvbmZpZGVuY2UpLAogICAgICAgICBjb25maWRlbmNlID0gY2FyOjpsb2dpdChjb25maWRlbmNlKSkgJT4lCiAgbXV0YXRlKHF1ZXJ5X2JwID0gKHF1ZXJ5X2JwIC0gbWVhbihxdWVyeV9icCkpL3NkKHF1ZXJ5X2JwKSwKICAgICAgICAgYmFzZWZyZXF1ZW5jeV9zZCA9IChiYXNlZnJlcXVlbmN5X3NkIC0gbWVhbihiYXNlZnJlcXVlbmN5X3NkKSkvc2QoYmFzZWZyZXF1ZW5jeV9zZCksCiAgICAgICAgIE5fc2FtcGxlcyA9IChOX3NhbXBsZXMgLSBtZWFuKE5fc2FtcGxlcykpL3NkKE5fc2FtcGxlcykKICAgICAgICAgKSAKCmZ1bGxfbW9kZWwgPSBsbShmb3JtdWxhID0gY29uZmlkZW5jZX5xdWVyeV9icCpiYXNlZnJlcXVlbmN5X3NkKk5fc2FtcGxlcywgZGF0YSA9IGxtX2RhdGEpIApmdWxsX21vZGVsCnN1bW1hcnkoZnVsbF9tb2RlbCkKcGxvdChmdWxsX21vZGVsKQpgYGAKCmBgYHtyfQpyZWR1Y2VkX21vZGVsID0gc3RlcChmdWxsX21vZGVsLCBkaXJlY3Rpb24gPSJib3RoIikKcmVkdWNlZF9tb2RlbApzdW1tYXJ5KHJlZHVjZWRfbW9kZWwpCnBsb3QocmVkdWNlZF9tb2RlbCkKYGBgCgoKCgoKIyMgU2ttZXIKCkZvciBza21lciwgd2UgbGVmdCBlYWNoIHNhbXBsZSBvdXQsIGJ1aWx0IGEgcmVmZXJlbmNlIGFuZCB0aGVuIHF1ZXJpZWQgdGhhdCBzYW1wbGUuIFdlIGhhdmUgc2V2ZXJhbCBmaWxlcyBpbiB3aGljaCByZWZlcmVuY2Ugc2FtcGxlcyBhcmUgb3JkZXJlZCBieSB0aGVpciBkaXN0YW5jZSB0byB0aGUgcXVlcnksIHdlIGhlcmUgd2Ugd2lsbCBldmFsdWF0ZSB3aGV0aGVyIHRoZSBjbG9zZXN0IHNhbXBsZSBpcyBmcm9tIHRoZSBjb3JyZWN0IHNwZWNpZXMgb3IgZ2VudXMuCgpCZWNhdXNlIGl0IGlzIG5vdCBjbGVhciBob3cgc2ttZXIgYmVoYXZlcyBmb3IgZGlmZmVyZW50IGxldmVscyBvZiBjb3ZlcmFnZSwgd2UgcmVwZWF0ZWQgdGhpcyBmb3Igc2V2ZXJhbCBpbnB1dCBzaXplcyAoaW4gbnVtYmVyIG9mIGJhc2VwYWlycykgYXMgcXVlcnksIGJ1dCBhbHdheXMgdXNlZCB0aGUgbWF4aW11bSBpbnB1dCBkaXplIGF2YWlsYWJsZSAodXAgdG8gMjAwTWIpIGZvciByZWZlcmVuY2VzLgoKTGV0J3MgbWFrZSBhIGZ1bmN0aW9uIHRoYXQgZXh0cmFjdHMgdGhlc2UgcmVzdWx0cyBhcyBhIHRhYmxlLgoKYGBge3J9CgpzYW1wX2xhYmVscyA9IHJlc3VsdHMgJT4lIHNlbGVjdChzYW1wbGVfaWQsYWN0dWFsX2xhYmVscykgJT4lIGRpc3RpbmN0KCkKCmV4dHJhY3Rfc2ttZXJfcmVzdWx0cyA9IGZ1bmN0aW9uKGZpbGVfcGF0aCkgewogICAgIyBSZWFkIG9ubHkgdGhlIGZpcnN0IDIgbGluZXMgb2YgdGhlIGZpbGUKICAgIGZpbGVfbGluZXMgPC0gcmVhZExpbmVzKGZpbGVfcGF0aCwgbiA9IDIpCiAgICAKICAgICMgRXh0cmFjdCBzYW1wbGVfSUQsIGJhc2VwYWlycyBmcm9tIHRoZSBmaXJzdCBsaW5lCiAgICBzYW1wbGVfaW5mbyA8LSBzdHJfbWF0Y2goZmlsZV9saW5lc1sxXSwgIlxccyooLio/KUAoXFxkK0spIilbLCAyOjNdCiAgICBzYW1wbGVfSUQgPC0gc2FtcGxlX2luZm9bMV0KICAgIGJhc2VwYWlycyA8LSBzYW1wbGVfaW5mb1syXQogICAgCiAgICAjIEV4dHJhY3QgcmVmZXJlbmNlX3NhbXBsZV9JRCwgZGlzdGFuY2UgZnJvbSB0aGUgc2Vjb25kIGxpbmUKICAgIHJlZmVyZW5jZV9pbmZvIDwtIHN0cl9tYXRjaChmaWxlX2xpbmVzWzJdLCAiXFxzKiguKj8pQC4qXFxzKyhcXGQrXFwuXFxkKykiKVssIDI6M10KICAgIHJlZmVyZW5jZV9zYW1wbGVfSUQgPC0gcmVmZXJlbmNlX2luZm9bMV0KICAgIGRpc3RhbmNlIDwtIGFzLm51bWVyaWMocmVmZXJlbmNlX2luZm9bMl0pCiAgICAKICAgICMgQ3JlYXRlIGEgdGliYmxlCiAgICB0aWJibGUoCiAgICAgICAgc2FtcGxlX2lkID0gc2FtcGxlX0lELAogICAgICAgIHF1ZXJ5X2JwID0gYmFzZXBhaXJzLAogICAgICAgIGNsb3Nlc3RfcmVmZXJlbmNlX3NhbXBsZV9pZCA9IHJlZmVyZW5jZV9zYW1wbGVfSUQsCiAgICAgICAgY2xvc2VzdF9kaXN0YW5jZSA9IGRpc3RhbmNlCiAgICApIAp9CmBgYAoKTm93IHdlIHdpbGwgYXBwbHkgdGhpcyBmdW5jdGlvbiB0byBhbGwgc2ttZXIgb3V0cHV0IGZpbGVzLgoKYGBge3J9CnBsYW4obXVsdGlzZXNzaW9uKHdvcmtlcnMgPSAxMikpCnNrbWVyX3Jlc3VsdHNfZGYgPSBmdXJycjo6ZnV0dXJlX21hcF9kZnIoCiAgbGlzdC5maWxlcygnTWFscGlnaGlhY2VhZStDaHJ5c29iYWxhbmFjZWFlL3NrbWVyL3NrbWVyX3h2YWxfcmVzdWx0cy8nLCBmdWxsLm5hbWVzID0gVCksCiAgfiBleHRyYWN0X3NrbWVyX3Jlc3VsdHMoLngpCikgJT4lCiAgbGVmdF9qb2luKHNhbXBfbGFiZWxzLCBieSA9ICdzYW1wbGVfaWQnKSAlPiUKICBsZWZ0X2pvaW4oCiAgICBzYW1wX2xhYmVscyAlPiUgc2VsZWN0KAogICAgICBjbG9zZXN0X3JlZmVyZW5jZV9zYW1wbGVfaWQgPSAnc2FtcGxlX2lkJywKICAgICAgcHJlZGljdGVkX2xhYmVscyA9IGFjdHVhbF9sYWJlbHMKICAgICksCiAgICBieSA9ICdjbG9zZXN0X3JlZmVyZW5jZV9zYW1wbGVfaWQnCiAgKSAlPiUKICBtdXRhdGUoCiAgICBxdWVyeV9sYWJlbHMgPSBzdHJfcmVtb3ZlKGFjdHVhbF9sYWJlbHMsICI7Kmxvd19xdWFsaXR5OlRydWU7KiIpICU+JSBzdHJfc3BsaXQoJzsnKSwKICAgIHByZWRpY3RlZF9saXN0ID0gc3RyX3NwbGl0KHByZWRpY3RlZF9sYWJlbHMsICc7JykKICApICU+JQogIHJvd3dpc2UoKSAlPiUKICBtdXRhdGUoCiAgICBmYW1pbHlfY29ycmVjdCA9IHF1ZXJ5X2xhYmVsc1tzdHJfZGV0ZWN0KHF1ZXJ5X2xhYmVscywgJ2ZhbWlseScpXSAlaW4lIHByZWRpY3RlZF9saXN0LAogICAgZ2VudXNfY29ycmVjdCA9IHF1ZXJ5X2xhYmVsc1tzdHJfZGV0ZWN0KHF1ZXJ5X2xhYmVscywgJ2dlbnVzJyldICVpbiUgcHJlZGljdGVkX2xpc3QsCiAgICBzcGVjaWVzX2NvcnJlY3QgPSBpZmVsc2UoYW55KHN0cl9kZXRlY3QoCiAgICAgIHF1ZXJ5X2xhYmVscywgJ3NwZWNpZXMnCiAgICApKSwKICAgIHF1ZXJ5X2xhYmVsc1tzdHJfZGV0ZWN0KHF1ZXJ5X2xhYmVscywgJ3NwZWNpZXMnKV0gJWluJSBwcmVkaWN0ZWRfbGlzdCwKICAgIE5BKSwKICAgIGZhbWlseV9pbmNvcnJlY3QgPSBhbnkoIShwcmVkaWN0ZWRfbGlzdFtzdHJfZGV0ZWN0KHByZWRpY3RlZF9saXN0LCAnZmFtaWx5JyldICVpbiUgcXVlcnlfbGFiZWxzW3N0cl9kZXRlY3QocXVlcnlfbGFiZWxzLCAnZmFtaWx5JyldKSksCiAgICBnZW51c19pbmNvcnJlY3QgPSBhbnkoIShwcmVkaWN0ZWRfbGlzdFtzdHJfZGV0ZWN0KHByZWRpY3RlZF9saXN0LCAnZ2VudXMnKV0gJWluJSBxdWVyeV9sYWJlbHNbc3RyX2RldGVjdChxdWVyeV9sYWJlbHMsICdnZW51cycpXSkpLAogICAgc3BlY2llc19pbmNvcnJlY3QgPSBpZmVsc2UoYW55KHN0cl9kZXRlY3QoCiAgICAgIHF1ZXJ5X2xhYmVscywgJ3NwZWNpZXMnCiAgICApKSwKICAgIGFueSghKAogICAgICBwcmVkaWN0ZWRfbGlzdFtzdHJfZGV0ZWN0KHByZWRpY3RlZF9saXN0LCAnc3BlY2llcycpXSAlaW4lIHF1ZXJ5X2xhYmVsc1tzdHJfZGV0ZWN0KHF1ZXJ5X2xhYmVscywgJ3NwZWNpZXMnKV0KICAgICkpLAogICAgTkEpCiAgICAKICApCnBsYW4oc2VxdWVudGlhbCkKc2ttZXJfcmVzdWx0c19kZgpgYGAKTm93IGxldCdzIHN1bW1hcml6ZSBhbmQgcGxvdCBieSBnZW51czoKCmBgYHtyfQpza21lcl9zdW1tYXJ5X2dlbnVzID0gc3VtbWFyaXplX3Jlc3VsdHMoc2ttZXJfcmVzdWx0c19kZiwnZ2VudXMnKQpwX3NrbWVyX2dlbnVzID0gcGxvdF9hcmVhKHNrbWVyX3N1bW1hcnlfZ2VudXMsICdTa21lciBnZW51cycsIHJlbGF0aXZlID0gVFJVRSkKcF9za21lcl9nZW51cwpgYGAKTm93IGJ5IHNwZWNpZXMuIEluIFNrbWVyLCB0aGVyZSBpcyBubyBpbmNvbmNsdXNpdmUgcmVzdWx0OiBpZiB0aGVyZSBpcyBubyBjb3JyZWN0IHNwZWNpZXMgcHJlZGljdGlvbiwgaXQgbWVhbnMgdGhhdCBhIHNhbXBsZSB3YXMgcHJlZGljdGVkIGluIHRoZSB3cm9uZyBnZW51cyBhbmQgdGhlcmVmb3JlIGl0IGlzIGluY29ycmVjdAoKYGBge3J9CnNrbWVyX3N1bW1hcnlfc3BlY2llcyA9IHN1bW1hcml6ZV9yZXN1bHRzKHNrbWVyX3Jlc3VsdHNfZGYsJ3NwZWNpZXMnKSAlPiUKICBtdXRhdGUocmVzdWx0ID0gaWZlbHNlKHJlc3VsdCA9PSAnY29ycmVjdCcsICdjb3JyZWN0JywnaW5jb3JyZWN0JykpICU+JQogIGdyb3VwX2J5KHF1ZXJ5X2JwLHJlc3VsdCkgJT4lCiAgc3VtbWFyaXNlX2FsbChzdW0pCnBfc2ttZXJfc3BlY2llcyA9IHBsb3RfYXJlYShza21lcl9zdW1tYXJ5X3NwZWNpZXMsICdTa21lciBzcGVjaWVzJywgcmVsYXRpdmUgPSBUUlVFKQpwX3NrbWVyX3NwZWNpZXMKYGBgCgpBbmQgbm93IGJ5IGZhbWlseToKCmBgYHtyfQpza21lcl9zdW1tYXJ5X2ZhbWlseSA9IHN1bW1hcml6ZV9yZXN1bHRzKHNrbWVyX3Jlc3VsdHNfZGYsJ2ZhbWlseScpCnNrbWVyX3N1bW1hcnlfZmFtaWx5IApwX3NrbWVyX2ZhbWlseSA9IHBsb3RfYXJlYShza21lcl9zdW1tYXJ5X2ZhbWlseSwgJ1NrbWVyIGZhbWlseScsIHJlbGF0aXZlID0gVFJVRSkKcF9za21lcl9mYW1pbHkKYGBgCgojIFRyYWRpdGlvbmFsIGJhcmNvZGVzCiMjIEJMQVNUIHNpbmdsZSBnZW5lCkxldCdzIG5vdyByZWFkIHRoZSB0cmFkaXRpb25hbCBiYXJjb2RlIEJMQVNUIHJlc3VsdHMgYW5kIHN1bW1hcml6ZSB0aGVtIGluIHRoZSBzYW1lIHdheSBhcyBza21lciBhbmQgdmFyS29kZXIuIExldCdzIHN0YXJ0IGJ5IGRlZmluaW5nIGEgZnVjdGlvbiB0aGF0IHJlYWRzIHRoZSBkYXRhIHNvIHdlIGNhbiBzdW1tYXJpemUgaXQgdXNpbmcgdGhlIHByZXZpb3VzbHkgZGVmaW5lZCBmdW5jdGlvbnMuCgpgYGB7cn0KcmVhZF90cmFkaXRpb25hbF9iYXJjb2RlcyA9IGZ1bmN0aW9uKGJwKSB7CiAgaW5wdXRfZmlsZSA9IHBhc3RlMCgKICAgICdNYWxwaWdoaWFjZWFlK0Nocnlzb2JhbGFuYWNlYWUvdHJhZGl0aW9uYWxfYmFyY29kZXMvMl9ibGFzdF9waHlsb2dlbnlfcmVzdWx0L0dlbnVzLycsCiAgICBicCwKICAgICdNX2JsYXN0X3BoeWxvX3N1bV9zcC50c3YnCiAgKQogIAogIGJhcmNvZGVfcmVzID0gcmVhZF9kZWxpbShpbnB1dF9maWxlKSAlPiUKICAgIHBpdm90X2xvbmdlcigtc3AsIG5hbWVzX3RvID0gJ21hcmtlcicsIHZhbHVlc190byA9ICdjbG9zZXN0X3JlZmVyZW5jZV9zYW1wbGVfaWQnKSAlPiUKICAgIHJlbmFtZShzYW1wbGVfaWQgPSAnc3AnKSAlPiUKICAgIG11dGF0ZSgKICAgICAgc2FtcGxlX2lkID0gc3RyX3JlbW92ZV9hbGwoc2FtcGxlX2lkLCAnQC4rJyksCiAgICAgIGNsb3Nlc3RfcmVmZXJlbmNlX3NhbXBsZV9pZCA9IHN0cl9yZW1vdmVfYWxsKGNsb3Nlc3RfcmVmZXJlbmNlX3NhbXBsZV9pZCwgJ0AuKycpLAogICAgICBwcmVkaWN0ZWRfbGFiZWxzID0gc2FtcF9sYWJlbHMkYWN0dWFsX2xhYmVsc1ttYXRjaChjbG9zZXN0X3JlZmVyZW5jZV9zYW1wbGVfaWQsIHNhbXBfbGFiZWxzJHNhbXBsZV9pZCldLAogICAgICBhY3R1YWxfbGFiZWxzID0gc2FtcF9sYWJlbHMkYWN0dWFsX2xhYmVsc1ttYXRjaChzYW1wbGVfaWQsIHNhbXBfbGFiZWxzJHNhbXBsZV9pZCldCiAgICApICU+JQogICAgZmlsdGVyKG1hcmtlciAhPSAnQ29uY2F0ZW5hdGVkX3BoeWxvZ2VueScpICU+JQogICAgbXV0YXRlKAogICAgICBxdWVyeV9sYWJlbHMgPSBzdHJfcmVtb3ZlKGFjdHVhbF9sYWJlbHMsICI7Kmxvd19xdWFsaXR5OlRydWU7KiIpICU+JSBzdHJfc3BsaXQoJzsnKSwKICAgICAgcHJlZGljdGVkX2xpc3QgPSBzdHJfc3BsaXQocHJlZGljdGVkX2xhYmVscywgJzsnKQogICAgKSAlPiUKICAgIHJvd3dpc2UoKSAlPiUKICAgIG11dGF0ZSgKICAgICAgZmFtaWx5X2NvcnJlY3QgPSBxdWVyeV9sYWJlbHNbc3RyX2RldGVjdChxdWVyeV9sYWJlbHMsICdmYW1pbHknKV0gJWluJSBwcmVkaWN0ZWRfbGlzdCwKICAgICAgZ2VudXNfY29ycmVjdCA9IHF1ZXJ5X2xhYmVsc1tzdHJfZGV0ZWN0KHF1ZXJ5X2xhYmVscywgJ2dlbnVzJyldICVpbiUgcHJlZGljdGVkX2xpc3QsCiAgICAgIHNwZWNpZXNfY29ycmVjdCA9IGlmZWxzZShhbnkoc3RyX2RldGVjdCgKICAgICAgICBxdWVyeV9sYWJlbHMsICdzcGVjaWVzJwogICAgICApKSwKICAgICAgcXVlcnlfbGFiZWxzW3N0cl9kZXRlY3QocXVlcnlfbGFiZWxzLCAnc3BlY2llcycpXSAlaW4lIHByZWRpY3RlZF9saXN0LAogICAgICBOQSksCiAgICAgIGZhbWlseV9pbmNvcnJlY3QgPSBhbnkoIShwcmVkaWN0ZWRfbGlzdFtzdHJfZGV0ZWN0KHByZWRpY3RlZF9saXN0LCAnZmFtaWx5JyldICVpbiUgcXVlcnlfbGFiZWxzW3N0cl9kZXRlY3QocXVlcnlfbGFiZWxzLCAnZmFtaWx5JyldKSksCiAgICAgIGdlbnVzX2luY29ycmVjdCA9IGFueSghKHByZWRpY3RlZF9saXN0W3N0cl9kZXRlY3QocHJlZGljdGVkX2xpc3QsICdnZW51cycpXSAlaW4lIHF1ZXJ5X2xhYmVsc1tzdHJfZGV0ZWN0KHF1ZXJ5X2xhYmVscywgJ2dlbnVzJyldKSksCiAgICAgIHNwZWNpZXNfaW5jb3JyZWN0ID0gaWZlbHNlKGFueShzdHJfZGV0ZWN0KAogICAgICAgIHF1ZXJ5X2xhYmVscywgJ3NwZWNpZXMnCiAgICAgICkpLAogICAgICBhbnkoISgKICAgICAgICBwcmVkaWN0ZWRfbGlzdFtzdHJfZGV0ZWN0KHByZWRpY3RlZF9saXN0LCAnc3BlY2llcycpXSAlaW4lIHF1ZXJ5X2xhYmVsc1tzdHJfZGV0ZWN0KHF1ZXJ5X2xhYmVscywgJ3NwZWNpZXMnKV0KICAgICAgKSksCiAgICAgIE5BKQogICAgKSAlPiUKICAgIG11dGF0ZV9hdCh2YXJzKGVuZHNfd2l0aCgiX2NvcnJlY3QiKSwgZW5kc193aXRoKCJfaW5jb3JyZWN0IikpLAogICAgICAgICAgICAgIH4gaWZlbHNlKGlzLm5hKHByZWRpY3RlZF9sYWJlbHMpICYgIWlzLm5hKC4pLCBGQUxTRSwgLikpICU+JQogICAgbXV0YXRlKHF1ZXJ5X2JwID0gYnAgKiAxZTMpCiAgCiAgcmV0dXJuKGJhcmNvZGVfcmVzKQp9CmBgYAoKCk5vdyB3ZSBjYW4gYXBwbHkgdGhpcyBmdW5jdGlvbiB0byBhbGwgb2Ygb3VyIHJlc3VsdHM6CgpgYGB7cn0KcmVzdWx0c19iYXJjb2RlcyA9IHB1cnJyOjptYXBfZGZyKGMoMTAsMjAsNTAsMTAwLDIwMCkscmVhZF90cmFkaXRpb25hbF9iYXJjb2RlcykKcmVzdWx0c19iYXJjb2RlcwpgYGAKCk5vdyBsZXQncyBzdW1tYXJpc2UgZm9yIGVhY2ggbWFya2VyIHNlcGFyYXRlbHk6CmBgYHtyfQpiYXJjb2RlX3N1bW1hcnlfZmFtaWx5ID0gc3BsaXQocmVzdWx0c19iYXJjb2RlcyxyZXN1bHRzX2JhcmNvZGVzJG1hcmtlcikgJT4lCiAgcHVycnI6Om1hcF9kZnIofnN1bW1hcml6ZV9yZXN1bHRzKC54LCdmYW1pbHknKSwuaWQ9J21hcmtlcicpCgpiYXJjb2RlX3N1bW1hcnlfZmFtaWx5CmBgYAoKYGBge3J9CmJhcmNvZGVfc3VtbWFyeV9nZW51cyA9IHNwbGl0KHJlc3VsdHNfYmFyY29kZXMscmVzdWx0c19iYXJjb2RlcyRtYXJrZXIpICU+JQogIHB1cnJyOjptYXBfZGZyKH5zdW1tYXJpemVfcmVzdWx0cygueCwnZ2VudXMnKSwuaWQ9J21hcmtlcicpCgpiYXJjb2RlX3N1bW1hcnlfZ2VudXMKYGBgCgpgYGB7cn0KYmFyY29kZV9zdW1tYXJ5X3NwZWNpZXMgPSBzcGxpdChyZXN1bHRzX2JhcmNvZGVzLHJlc3VsdHNfYmFyY29kZXMkbWFya2VyKSAlPiUKICBwdXJycjo6bWFwX2Rmcih+c3VtbWFyaXplX3Jlc3VsdHMoLngsJ3NwZWNpZXMnKSwuaWQ9J21hcmtlcicpCgpiYXJjb2RlX3N1bW1hcnlfc3BlY2llcwpgYGAKCk5vdyBsZXQncyBwbG90LCBtYWtpbmcgc2VwYXJhdGUgcGxvdHMgZm9yIGVhY2ggbWFya2VyOgoKU3BlY2llczoKYGBge3J9CnBfYmFyY29kZV9zcGVjaWVzID0gYmFyY29kZV9zdW1tYXJ5X3NwZWNpZXMgJT4lCiAgc3BsaXQoYmFyY29kZV9zdW1tYXJ5X3NwZWNpZXMkbWFya2VyKSAlPiUKICBwdXJycjo6bWFwKH5wbG90X2FyZWEoLngscGFzdGUwKHVuaXF1ZSgueCRtYXJrZXIpLCcgc3BlY2llcycpLCByZWxhdGl2ZSA9IFRSVUUpKQoKcF9iYXJjb2RlX3NwZWNpZXMKYGBgCkdlbmVyYToKYGBge3J9CnBfYmFyY29kZV9nZW51cyA9IGJhcmNvZGVfc3VtbWFyeV9nZW51cyAlPiUKICBzcGxpdChiYXJjb2RlX3N1bW1hcnlfZ2VudXMkbWFya2VyKSAlPiUKICBwdXJycjo6bWFwKH5wbG90X2FyZWEoLngscGFzdGUwKHVuaXF1ZSgueCRtYXJrZXIpLCcgZ2VudXMnKSwgcmVsYXRpdmUgPSBUUlVFKSkKCnBfYmFyY29kZV9nZW51cwpgYGAKRmFtaWx5OgpgYGB7cn0KcF9iYXJjb2RlX2ZhbWlseSA9IGJhcmNvZGVfc3VtbWFyeV9mYW1pbHkgJT4lCiAgc3BsaXQoYmFyY29kZV9zdW1tYXJ5X2ZhbWlseSRtYXJrZXIpICU+JQogIHB1cnJyOjptYXAofnBsb3RfYXJlYSgueCxwYXN0ZTAodW5pcXVlKC54JG1hcmtlciksJyBmYW1pbHknKSwgcmVsYXRpdmUgPSBUUlVFKSkKCnBfYmFyY29kZV9mYW1pbHkKYGBgCgojIyBDb25jYXRlbmF0ZWQgdHJlZQpOb3cgd2Ugd2lsbCBkbyB0aGUgc2FtZSBmb3IgY29uY2F0ZW5hdGVkIHRyZWUuIExldCdzIHN0YXJ0IGJ5IGRlZmluaW5nIGEgZnVuY3Rpb24gdG8gZ2F0aGVyIHJlc3VsdHMuIFdlIHdpbGwgY29uc2lkZXIgYSByZXN1bHQgYXMgY29ycmVjdCBpZiB0aGUgbWFqb3JpdHkgb2YgdGhlIHNpc3RlciB0YXhvbiB0byBhIHRpcCBoYXMgdGhlIHNhbWUgbGFiZWwuCgpDT05USU5VRSBBRlRFUiBXRSBHRVQgVEhFIEZJTkFMIERBVEFTRVQKCmBgYHtyfQoKcmVhZF9jb25jYXRlbmF0ZWRfdHJlZV9yZXN1bHRzID0gZnVuY3Rpb24oYnApewojIFJlYWQgaW4geW91ciB0cmVlIC0gcmVwbGFjZSAneW91cl90cmVlX2ZpbGUubndrJyB3aXRoIHRoZSBwYXRoIHRvIHlvdXIgdHJlZSBmaWxlCnRyZWUgPSByZWFkLnRyZWUocGFzdGUwKCdNYWxwaWdoaWFjZWFlK0Nocnlzb2JhbGFuYWNlYWUvdHJhZGl0aW9uYWxfYmFyY29kZXMvMl9ibGFzdF9waHlsb2dlbnlfcmVzdWx0L0dlbnVzL2NvbmMuJyxicCwnbS5zcG5hbWUudHJlZWZpbGUnKSkKCiNsZWF2ZSBvbmx5IHNhbXBsZSBJRHMgYXMgdGlwIGxhYmVscwp0cmVlJHRpcC5sYWJlbCA9IHRyZWUkdGlwLmxhYmVsICU+JSBzdHJfcmVtb3ZlKCIuKkAiKQoKIyBDb21wdXRlIHRoZSBwYXRyaXN0aWMgZGlzdGFuY2VzIGFuZCBsaXN0IGFsbCByZWZlcmVuY2UgbmFtZXMKcGF0cmlzdGljX2Rpc3RhbmNlcyA8LSBjb3BoZW5ldGljKHRyZWUpCmFsbF9yZWZfbmFtZXMgPSBkaW1uYW1lcyhwYXRyaXN0aWNfZGlzdGFuY2VzKVtbMV1dW3N0cl9kZXRlY3QoZGltbmFtZXMocGF0cmlzdGljX2Rpc3RhbmNlcylbWzFdXSwnX3JlZiQnKV0KYWxsX25vbnJlZiA9IGRpbW5hbWVzKHBhdHJpc3RpY19kaXN0YW5jZXMpW1sxXV1bc3RyX2RldGVjdChkaW1uYW1lcyhwYXRyaXN0aWNfZGlzdGFuY2VzKVtbMV1dLCdfcmVmJCcsbmVnYXRlID0gVFJVRSldCgojIEZvciBlYWNoIHRpcCwgZmluZCB0aGUgcmVmZXJlbmNlIHNhbXBsZSB3aXRoIGNsb3Nlc3QgcGF0cmlzdGljIGRpc3RhbmNlCmZpbmRfY2xvc2VzdCA9IGZ1bmN0aW9uKHRpcCl7CiAgdG9fa2VlcCA9IGModGlwLGFsbF9yZWZfbmFtZXNbc3RyX2RldGVjdChhbGxfcmVmX25hbWVzLHBhc3RlMCh0aXAsJ19yZWYnKSxuZWdhdGUgPSBUUlVFKV0pCiAgcmV0dXJuKG5hbWVzKHNvcnQocGF0cmlzdGljX2Rpc3RhbmNlc1t0aXAsdG9fa2VlcF0pWzJdKSAlPiUKICAgICAgICAgICBzdHJfcmVtb3ZlKCdfcmVmJykpCn0KCmNsb3Nlc3RfbWF0Y2ggPSBwdXJycjo6bWFwX2NocihhbGxfbm9ucmVmLGZpbmRfY2xvc2VzdCkKCmJhcmNvZGVfcmVzID0gdGliYmxlKHNhbXBsZV9pZCA9IGFsbF9ub25yZWYsCiAgICAgICBjbG9zZXN0X3JlZmVyZW5jZV9zYW1wbGVfaWQgPSBjbG9zZXN0X21hdGNoKSAlPiUKICBtdXRhdGUoCiAgICAgIHByZWRpY3RlZF9sYWJlbHMgPSBzYW1wX2xhYmVscyRhY3R1YWxfbGFiZWxzW21hdGNoKGNsb3Nlc3RfcmVmZXJlbmNlX3NhbXBsZV9pZCwgc2FtcF9sYWJlbHMkc2FtcGxlX2lkKV0sCiAgICAgIGFjdHVhbF9sYWJlbHMgPSBzYW1wX2xhYmVscyRhY3R1YWxfbGFiZWxzW21hdGNoKHNhbXBsZV9pZCwgc2FtcF9sYWJlbHMkc2FtcGxlX2lkKV0KICAgICkgJT4lCiAgZmlsdGVyKHNhbXBsZV9pZCE9JzIwOTUnKSAlPiUKICBtdXRhdGUoCiAgICAgIHF1ZXJ5X2xhYmVscyA9IHN0cl9yZW1vdmUoYWN0dWFsX2xhYmVscywgIjsqbG93X3F1YWxpdHk6VHJ1ZTsqIikgJT4lIHN0cl9zcGxpdCgnOycpLAogICAgICBwcmVkaWN0ZWRfbGlzdCA9IHN0cl9zcGxpdChwcmVkaWN0ZWRfbGFiZWxzLCAnOycpCiAgICApICU+JQogICAgcm93d2lzZSgpICU+JQogICAgbXV0YXRlKAogICAgICBmYW1pbHlfY29ycmVjdCA9IHF1ZXJ5X2xhYmVsc1tzdHJfZGV0ZWN0KHF1ZXJ5X2xhYmVscywgJ2ZhbWlseScpXSAlaW4lIHByZWRpY3RlZF9saXN0LAogICAgICBnZW51c19jb3JyZWN0ID0gcXVlcnlfbGFiZWxzW3N0cl9kZXRlY3QocXVlcnlfbGFiZWxzLCAnZ2VudXMnKV0gJWluJSBwcmVkaWN0ZWRfbGlzdCwKICAgICAgc3BlY2llc19jb3JyZWN0ID0gaWZlbHNlKGFueShzdHJfZGV0ZWN0KAogICAgICAgIHF1ZXJ5X2xhYmVscywgJ3NwZWNpZXMnCiAgICAgICkpLAogICAgICBxdWVyeV9sYWJlbHNbc3RyX2RldGVjdChxdWVyeV9sYWJlbHMsICdzcGVjaWVzJyldICVpbiUgcHJlZGljdGVkX2xpc3QsCiAgICAgIE5BKSwKICAgICAgZmFtaWx5X2luY29ycmVjdCA9IGFueSghKHByZWRpY3RlZF9saXN0W3N0cl9kZXRlY3QocHJlZGljdGVkX2xpc3QsICdmYW1pbHknKV0gJWluJSBxdWVyeV9sYWJlbHNbc3RyX2RldGVjdChxdWVyeV9sYWJlbHMsICdmYW1pbHknKV0pKSwKICAgICAgZ2VudXNfaW5jb3JyZWN0ID0gYW55KCEocHJlZGljdGVkX2xpc3Rbc3RyX2RldGVjdChwcmVkaWN0ZWRfbGlzdCwgJ2dlbnVzJyldICVpbiUgcXVlcnlfbGFiZWxzW3N0cl9kZXRlY3QocXVlcnlfbGFiZWxzLCAnZ2VudXMnKV0pKSwKICAgICAgc3BlY2llc19pbmNvcnJlY3QgPSBpZmVsc2UoYW55KHN0cl9kZXRlY3QoCiAgICAgICAgcXVlcnlfbGFiZWxzLCAnc3BlY2llcycKICAgICAgKSksCiAgICAgIGFueSghKAogICAgICAgIHByZWRpY3RlZF9saXN0W3N0cl9kZXRlY3QocHJlZGljdGVkX2xpc3QsICdzcGVjaWVzJyldICVpbiUgcXVlcnlfbGFiZWxzW3N0cl9kZXRlY3QocXVlcnlfbGFiZWxzLCAnc3BlY2llcycpXQogICAgICApKSwKICAgICAgTkEpCiAgICApICU+JQogICAgbXV0YXRlX2F0KHZhcnMoZW5kc193aXRoKCJfY29ycmVjdCIpLCBlbmRzX3dpdGgoIl9pbmNvcnJlY3QiKSksCiAgICAgICAgICAgICAgfiBpZmVsc2UoaXMubmEocHJlZGljdGVkX2xhYmVscykgJiAhaXMubmEoLiksIEZBTFNFLCAuKSkgJT4lCiAgICBtdXRhdGUocXVlcnlfYnAgPSBicCAqIDFlMykKICAKICByZXR1cm4oYmFyY29kZV9yZXMpCn0KCmBgYAoKTm93IGxldCdzIGFwcGx5IHRoaXMgZnVuY3Rpb24KYGBge3J9CnJlc3VsdHNfY29uY2F0X2JhcmNvZGVzID0gcHVycnI6Om1hcF9kZnIoYygxMCwyMCw1MCwxMDAsMjAwKSxyZWFkX2NvbmNhdGVuYXRlZF90cmVlX3Jlc3VsdHMpCnJlc3VsdHNfY29uY2F0X2JhcmNvZGVzCmBgYApMZXQncyBzdW1tYXJpemUgcmVzdWx0cyBhbmQgcGxvdCBmb3IgZ2VudXMsIHNwZWNpZXMgYW5kIGZhbWlseSBhY2N1cmFjeQoKYGBge3J9CmNvbmNhdF9zdW1tYXJ5X3NwZWNpZXMgPSBzdW1tYXJpemVfcmVzdWx0cyhyZXN1bHRzX2NvbmNhdF9iYXJjb2RlcywnZ2VudXMnKQpwX2NvbmNhdF9zcGVjaWVzID0gcGxvdF9hcmVhKGNvbmNhdF9zdW1tYXJ5X3NwZWNpZXMsIHJlbGF0aXZlID0gVFJVRSx0aXRsZSA9ICdDb25jYXRlbmF0ZWQgYmFyY29kZXMgc3BlY2llcycpCnBfY29uY2F0X3NwZWNpZXMKYGBgCgoKCgojIERpcmVjdCBjb21wYXJpc29uCgpOb3cgbGV0J3MgY29tcGFyZSBtZXRob2RzIHNpZGUgYnkgc2lkZS4gRm9yIGdlbnVzIGxldmVsOgpgYGB7ciBmaWcuaGVpZ2h0PTEwfQpyZWZvcm1hdF9ncmFwaHMgPSBmdW5jdGlvbih4KXsKICB4ICsKICAgIHRoZW1lKGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCkpICsKICAgIGxhYnModGl0bGUgPSBzdWIoIiAoZ2VudXN8c3BlY2llc3xmYW1pbHkpJCIsICIiLCB4JGxhYmVscyR0aXRsZSkpCn0KCnAgPSBwYXRjaHdvcms6OndyYXBfcGxvdHMocmVmb3JtYXRfZ3JhcGhzKHBfZ2VudXMpLCAKICAgICAgICAgICAgICAgICAgIHJlZm9ybWF0X2dyYXBocyhwX3NrbWVyX2dlbnVzKSwgCiAgICAgICAgICAgICAgICAgICByZWZvcm1hdF9ncmFwaHMocF9iYXJjb2RlX2dlbnVzJElUUyksCiAgICAgICAgICAgICAgICAgICBwX2JhcmNvZGVfZ2VudXMkcmJjTCArbGFicyh0aXRsZSA9IHN1YigiIGdlbnVzJCIsICIiLCBwX2JhcmNvZGVfZ2VudXMkcmJjTCRsYWJlbHMkdGl0bGUpKSwKICAgICAgICAgICAgICAgICAgIG5jb2wgPSAxKSArCiAgcGxvdF9hbm5vdGF0aW9uKHRpdGxlID0gJ0dlbnVzLWxldmVsIGFjY3VyYWN5JykKcApnZ3NhdmUoJ2ltYWdlc19tYW51c2NyaXB0L2ZpZzNfZ2VudXNfYWNjdXJhY3kucGRmJywgd2lkdGg9NC41LGhlaWdodCA9IDEwKQpnZ3NhdmUoJ2ltYWdlc19tYW51c2NyaXB0L2ZpZzNfZ2VudXNfYWNjdXJhY3kucG5nJywgd2lkdGg9NC41LGhlaWdodCA9IDEwLGRwaT0xMjAwKQpgYGAKTm93IGZvciBzcGVjaWVzIGxldmVsOgpgYGB7ciBmaWcuaGVpZ2h0ID0gMTB9CnAgPSBwYXRjaHdvcms6OndyYXBfcGxvdHMocmVmb3JtYXRfZ3JhcGhzKHBfc3BlY2llcyksCiAgICAgICAgICAgICAgICAgICByZWZvcm1hdF9ncmFwaHMocF9za21lcl9zcGVjaWVzKSwKICAgICAgICAgICAgICAgICAgIHJlZm9ybWF0X2dyYXBocyhwX2JhcmNvZGVfc3BlY2llcyRJVFMpLAogICAgICAgICAgICAgICAgICAgcF9iYXJjb2RlX3NwZWNpZXMkcmJjTCArIGxhYnModGl0bGUgPSBzdWIoIiBzcGVjaWVzJCIsICIiLCBwX2JhcmNvZGVfc3BlY2llcyRyYmNMJGxhYmVscyR0aXRsZSkpLAogICAgICAgICAgICAgICAgICAgbmNvbCA9IDEpICsKICBwbG90X2Fubm90YXRpb24odGl0bGUgPSAnU3BlY2llcy1sZXZlbCBhY2N1cmFjeScpCnAKZ2dzYXZlKCdpbWFnZXNfbWFudXNjcmlwdC9maWczX3NwZWNpZXNfYWNjdXJhY3kucGRmJywgd2lkdGg9NC41LGhlaWdodCA9IDEwKQpnZ3NhdmUoJ2ltYWdlc19tYW51c2NyaXB0L2ZpZzNfc3BlY2llc19hY2N1cmFjeS5wbmcnLCB3aWR0aD00LjUsaGVpZ2h0ID0gMTAsZHBpPTEyMDApCmBgYApOb3cgZm9yIHNwZWNpZXMgbGV2ZWw6CmBgYHtyIGZpZy5oZWlnaHQgPSAxMH0KcCA9IHBhdGNod29yazo6d3JhcF9wbG90cyhyZWZvcm1hdF9ncmFwaHMocF9mYW1pbHkpLAogICAgICAgICAgICAgICAgICAgcmVmb3JtYXRfZ3JhcGhzKHBfc2ttZXJfZmFtaWx5KSwKICAgICAgICAgICAgICAgICAgIHJlZm9ybWF0X2dyYXBocyhwX2JhcmNvZGVfZmFtaWx5JElUUyksCiAgICAgICAgICAgICAgICAgICBwX2JhcmNvZGVfZmFtaWx5JHJiY0wgKyBsYWJzKHRpdGxlID0gc3ViKCIgZmFtaWx5JCIsICIiLCBwX2JhcmNvZGVfZmFtaWx5JHJiY0wkbGFiZWxzJHRpdGxlKSksCiAgICAgICAgICAgICAgICAgICBuY29sID0gMSkgKwogIHBsb3RfYW5ub3RhdGlvbih0aXRsZSA9ICdmYW1pbHktbGV2ZWwgYWNjdXJhY3knKQpwCmdnc2F2ZSgnaW1hZ2VzX21hbnVzY3JpcHQvZmlnM19mYW1pbHlfYWNjdXJhY3kucGRmJywgd2lkdGg9NC41LGhlaWdodCA9IDEwKQpnZ3NhdmUoJ2ltYWdlc19tYW51c2NyaXB0L2ZpZzNfZmFtaWx5X2FjY3VyYWN5LnBuZycsIHdpZHRoPTQuNSxoZWlnaHQgPSAxMCxkcGk9MTIwMCkKYGBgCgojIENvbXBhcmlzb24gb2YgcnVuIHRpbWVzCgpOb3cgbGV0J3MgY29tcGFyZSB0aGUgdGltZSB0byBwcm9kdWNlIHJlZmVyZW5jZXMgYW5kIHRvIHByb2R1Y2UgCgojIFNSQQoKRmluYWxseSwgbGV0J3Mgc3VtbWFyaXplIHJlc3VsdHMgZm9yIHRoZSB3aG9sZSBTUkEgZGF0YXNldC4gSW4gdGhpcyBjYXNlLCB3ZSBvbmx5IGhhdmUgdmFyS29kZXIgc2luY2UgU2ttZXIgY2Fubm90IGZpbmlzaCBhbmQgdHJhZGl0aW9uYWwgYmFyY29kZXMgYXJlIGluYXBwbGljYWJsZS4KCmBgYHtyfQp2YXJLb2Rlcl9TUkFfcmVzdWx0cyAgPSByZWFkX2NzdignYWxsX1NSQS92YXJrb2Rlcl9xdWVyeV9yZXN1bHRzL3ByZWRpY3Rpb25zLmNzdicpICU+JQpzZWxlY3QoLTEpICU+JQogIGZpbHRlcihzdHJfZGV0ZWN0KHF1ZXJ5X2Jhc2VwYWlycywnXjArWzEyNV0wK0skJykpICU+JSAjd2Ugd2lsbCBpZ25vcmUgcXVlcmllcyB0aGF0IGFyZSBub3Qgc3RhbmRhcmRpemVkIHNpemVzCiAgcmVuYW1lKHF1ZXJ5X2JwID0gcXVlcnlfYmFzZXBhaXJzKSAlPiUKICBtdXRhdGUocXVhbGl0eV9pbmNsdWRlZCA9IFQpCnBsYW4oc2VxdWVudGlhbCkKClNSQV90YXhsYWJlbHMgPSBzdHJfcmVtb3ZlKHZhcktvZGVyX1NSQV9yZXN1bHRzJGFjdHVhbF9sYWJlbHMsIjsqbG93X3F1YWxpdHk6VHJ1ZTsqIikgJT4lIHN0cl9zcGxpdCgnOycpICU+JSB1bmxpc3QgJT4lIHVuaXF1ZQoKdmFyS29kZXJfU1JBX3Jlc3VsdHMgPSB2YXJLb2Rlcl9TUkFfcmVzdWx0cyAlPiUKICBtdXRhdGUocXVlcnlfbGFiZWxzID0gc3RyX3JlbW92ZShhY3R1YWxfbGFiZWxzLCI7Kmxvd19xdWFsaXR5OlRydWU7KiIpICU+JSBzdHJfc3BsaXQoJzsnKSAlPiUgdW5saXN0LAogICAgICAgICBwcmVkaWN0ZWRfbGlzdCA9IHN0cl9zcGxpdChwcmVkaWN0ZWRfbGFiZWxzLCc7JykKICAgICAgICAgKSAlPiUKICByb3d3aXNlKCkgJT4lCiAgbXV0YXRlKGZhbWlseV9jb3JyZWN0ID0gcXVlcnlfbGFiZWxzICVpbiUgcHJlZGljdGVkX2xpc3QsCiAgICAgICAgIGZhbWlseV9pbmNvcnJlY3QgPSBpZmVsc2UoaXMubmEocHJlZGljdGVkX2xhYmVscyksRkFMU0UsYW55KCEocHJlZGljdGVkX2xpc3QgJWluJSBxdWVyeV9sYWJlbHMpKSkpICU+JQogc2VsZWN0KG1hdGNoZXMoIl5bXjAtOV0iKSkKCnZhcktvZGVyX1NSQV9yZXN1bHRzIAogICAgICAgICAKYGBgCgpOb3cgbGV0J3Mgc3VtbWFyaXplIGFuZCBwbG90OgoKYGBge3J9ClNSQV9zdW1tYXJ5X2ZhbWlseSA9IHN1bW1hcml6ZV9yZXN1bHRzKHZhcktvZGVyX1NSQV9yZXN1bHRzLCdmYW1pbHknKQpTUkFfc3VtbWFyeV9mYW1pbHkKCk5fc2FtcCA9IFNSQV9zdW1tYXJ5X2ZhbWlseSAlPiUKIGdyb3VwX2J5KHF1ZXJ5X2JwKSAlPiUKIHN1bW1hcmlzZShOID0gc3VtKE4pKQoKcF9TUkFfZmFtaWx5ID0gcGxvdF9hcmVhKFNSQV9zdW1tYXJ5X2ZhbWlseSwgJ3ZhcktvZGVyIFNSQSBmYW1pbHknLCByZWxhdGl2ZSA9IFRSVUUpIApwX1NSQV9mYW1pbHkgCmBgYAoKTGV0J3Mgbm93IGRvIHRoZSBTUkEgcGxvdCwgYnV0IHNwbGl0dGluZyBieSBraW5nZG9tLiBGaXJzdCwgd2UgbmVlZCB0byByZXRyaWV2ZSBraW5nZG9tIGluZm9ybWF0aW9uOgpgYGB7cn0KCnBfU1JBX2ZhbWlsaWVzID0gcmVhZF9jc3YoJ2FsbF9TUkEvcnVuc190b19kb3dubG9hZF9kYXRhLmNzdicpICU+JQogIHNlbGVjdChzYW1wbGVfaWQgPSBSdW4sIEtpbmdkb20pICU+JQogIHJpZ2h0X2pvaW4odmFyS29kZXJfU1JBX3Jlc3VsdHMpICU+JQogIHNwbGl0KC4kS2luZ2RvbSkgJT4lCiAgcHVycnI6Om1hcChzdW1tYXJpemVfcmVzdWx0cywgCiAgICAgICAgICAgICAgICAgbGV2ZWw9J2ZhbWlseScpICU+JQogIHB1cnJyOjppbWFwKH5wbG90X2FyZWEoLngsLnkscmVsYXRpdmU9VFJVRSkgKyBjb29yZF9jYXJ0ZXNpYW4oeGxpbT1jKDUwMCwxMDAwMCkqMTAwMCxleHBhbmQgPSBGQUxTRSkpCgpwX1NSQV9mYW1pbGllcwpgYGAKCk5vdyBsZXQncyBqb2luIHRvIGNyZWF0ZSBhIHBsb3QgZm9yIHB1YmxpY2F0aW9uOgoKYGBge3J9CnJlbW92ZV95X2F4aXNfYW5kX3NjYWxlID0gZnVuY3Rpb24oeCl7CiAgeCArCiAgICB0aGVtZShheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGxlZ2VuZC5wb3NpdGlvbj0nbm9uZScsCiAgICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCkpCn0KCiMgTW9kaWZ5IHRoZSBsYXN0IHBsb3QgdG8gaGF2ZSB0aGUgeC1heGlzIGxhYmVsCnBfU1JBX2ZhbWlsaWVzJEZ1bmdpIDwtIHBfU1JBX2ZhbWlsaWVzJEZ1bmdpICsgbGFicyh4ID0gIkJhc2UgcGFpcnMgaW4gcXVlcnkgaW1hZ2VzIikKCiMgQ29tYmluZSBwbG90cwpwX2NvbWJpbmVkIDwtIHdyYXBfcGxvdHMocF9TUkFfZmFtaWxpZXMkTWV0YXpvYSArIAogICAgICAgICAgICAgICAgICAgICAgICAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAnbm9uZScsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSksCiAgICAgICAgICAgICAgICAgICAgICAgICByZW1vdmVfeV9heGlzX2FuZF9zY2FsZShwX1NSQV9mYW1pbGllcyRWaXJpZGlwbGFudGFlKSwKICAgICAgICAgICAgICAgICAgICAgICAgIHJlbW92ZV95X2F4aXNfYW5kX3NjYWxlKHBfU1JBX2ZhbWlsaWVzJEZ1bmdpKSwKICAgICAgICAgICAgICAgICAgICAgICAgIG5yb3cgPSAxKQoKIyBBZGQgdGl0bGUgYW5kIHNldCBsYXlvdXQKIyBDcmVhdGUgYSBibGFuayBnZ3Bsb3Qgb2JqZWN0IHdpdGggdGhlIGRlc2lyZWQgeC1heGlzIHRpdGxlCnhfdGl0bGVfcGxvdCA8LSBnZ3Bsb3QoKSArIAogIHRoZW1lX3ZvaWQoKSArIAogIGxhYnMoeCA9ICJCYXNlIHBhaXJzIGluIHF1ZXJ5IGltYWdlcyIpICsKICB0aGVtZShwbG90Lm1hcmdpbiA9IG1hcmdpbigwLCAwLCAwLCAwKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwLCBoanVzdCA9IDAuNSkpCgoKcCA9IHdyYXBfcGxvdHMocF9jb21iaW5lZCwgeF90aXRsZV9wbG90LG5jb2w9MSxoZWlnaHRzID0gYygwLjk1LDAuMDUpKQogICAgCiAgCgpwcmludChwKQoKCmdnc2F2ZSgnaW1hZ2VzX21hbnVzY3JpcHQvZmlnM19TUkFfYWNjdXJhY3kucGRmJywgd2lkdGg9NC41LGhlaWdodCA9IDQpCmdnc2F2ZSgnaW1hZ2VzX21hbnVzY3JpcHQvZmlnM19TUkFfYWNjdXJhY3kucG5nJywgd2lkdGg9NC41LGhlaWdodCA9IDQsZHBpID0gMTIwMCkKYGBgCgoKCiMgR2VuZXJhdGluZyBudW1iZXJzIGZvciBwdWJsaWNhdGlvbgoKSGVyZSB3ZSBqdXN0IHF1ZXJ5IG91ciByZXN1bHRzIHRvIGdldCBhIGZldyBmaWd1cmVzIHRoYXQgd2UgcmVwb3J0IGluIHRoZSBwYXBlci4KClRvdGFsIG51bWJlciBvZiBzYW1wbGVzIHVzZWQgaW4gY3Jvc3MtdmFsaWRhdGlvbjoKYGBge3J9CmRpbShzYW1wX2xhYmVscykKYGBgCgpOdW1iZXIgb2YgU3RpZ21hcGh5bGxvbiBzYW1wbGVzIHdpdGggZWFjaCBraW5kIG9mIGVycm9yIGZvciB2YXJrb2RlcjoKYGBge3J9CnN1bW1hcnlfc3BlY2llcwpgYGAKCk51bWJlciBvZiBTdGlnbWFwaHlsbG9uIHNhbXBsZXMgd2l0aCBlYWNoIGtpbmQgb2YgZXJyb3IgZm9yIHNrbWVyOgpgYGB7cn0Kc2ttZXJfc3VtbWFyeV9zcGVjaWVzCmBgYAp2YXJLb2RlciBhY2N1cmFjeSBmb3IgZ2VuZXJhOgpgYGB7cn0Kc3VtbWFyeV9nZW51cwpgYGAKdmFyS29kZXIgYWNjdXJhY3kgZm9yIGZhbWlseToKYGBge3J9CnN1bW1hcnlfZmFtaWx5CmBgYAoKCgpTa21lciBhY2N1cmFjeSBmb3IgZ2VuZXJhOgpgYGB7cn0Kc2ttZXJfc3VtbWFyeV9nZW51cwpgYGAKClNrbWVyIGFjY3VyYWN5IGZvciBmYW1pbHk6CmBgYHtyfQpza21lcl9zdW1tYXJ5X2ZhbWlseQpgYGAKCgpOdW1iZXIgb2Ygc2FtcGxlcyBhdmFpbGFibGUgZm9yIGVhY2ggZ2VudXMgYW5kIGRhdGEgYW1vdW50CmBgYHtyfQpyZXN1bHRzICU+JQogIG11dGF0ZShnZW51cyA9IHN0cl9leHRyYWN0KGFjdHVhbF9sYWJlbHMsIig/PD1nZW51czopW147XSsiKSkgJT4lCiAgZ3JvdXBfYnkocXVlcnlfYnApICU+JQogIHN1bW1hcml6ZShOPW4oKSkgJT4lCiAgY29tcGxldGUoKQpgYGAKUGxvdCBudW1iZXIgb2Ygc2FtcGxlcyBmb3Igc3VwcGxlbWVudGFyeSBtYXRlcmlhbC4KCmBgYHtyfQpuX3NhbXBsZXNfZ2VuZXJhID0gcmVzdWx0cyAlPiUKICBtdXRhdGUodGF4b24gPSBzdHJfZXh0cmFjdChhY3R1YWxfbGFiZWxzLCIoPzw9Z2VudXM6KVteO10rIikpICU+JQogIGdyb3VwX2J5KHRheG9uLCBxdWVyeV9icCkgJT4lCiAgc3VtbWFyaXplKE49bigpKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgY29tcGxldGUodGF4b24sIHF1ZXJ5X2JwLCBmaWxsID0gbGlzdChOPTApKQpuX3NhbXBsZXNfZ2VuZXJhIAoKbl9zYW1wbGVzX3NwZWNpZXMgPSByZXN1bHRzICU+JQogIG11dGF0ZSh0YXhvbiA9IHN0cl9leHRyYWN0KGFjdHVhbF9sYWJlbHMsIig/PD1zcGVjaWVzOilbXjtdKyIpKSAlPiUKICBmaWx0ZXIoIWlzLm5hKHRheG9uKSkgJT4lCiAgZ3JvdXBfYnkodGF4b24sIHF1ZXJ5X2JwKSAlPiUKICBzdW1tYXJpemUoTj1uKCkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBjb21wbGV0ZSh0YXhvbiwgcXVlcnlfYnAsIGZpbGwgPSBsaXN0KE49MCkpCm5fc2FtcGxlc19zcGVjaWVzIAoKbl9zYW1wbGVzX1NSQSA9IHZhcktvZGVyX1NSQV9yZXN1bHRzICU+JQogIG11dGF0ZSh0YXhvbiA9IGFzLmNoYXJhY3RlcihhY3R1YWxfbGFiZWxzKSkgJT4lCiAgZ3JvdXBfYnkodGF4b24sIHF1ZXJ5X2JwKSAlPiUKICBzdW1tYXJpemUoTj1uKCkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBjb21wbGV0ZSh0YXhvbiwgcXVlcnlfYnAsIGZpbGwgPSBsaXN0KE49MCkpCm5fc2FtcGxlc19TUkEgCmBgYApgYGB7cn0KcGxvdF9Oc2FtcGxlc19hcmVhID0gZnVuY3Rpb24oZGYsIHRpdGxlKXsKICBkZiA9IGRmICU+JSAKICAgIG11dGF0ZShxdWVyeV9icCA9IHBhcnNlX251bWJlcihxdWVyeV9icCkgKjEwMDApCiAgCiAgbl9sZXZlbHMgPC0gbGVuZ3RoKHVuaXF1ZShkZiR0YXhvbikpCiAgdmlyaWRpc19jb2xvcnMgPC0gdmlyaWRpczo6dHVyYm8obl9sZXZlbHMpCiAgCiAgaGFsZl9uIDwtIGNlaWxpbmcobl9sZXZlbHMgLyAyKQogIHJlb3JkZXJlZF9jb2xvcnMgPC0gYyhyYmluZCh2aXJpZGlzX2NvbG9yc1sxOmhhbGZfbl0sIHZpcmlkaXNfY29sb3JzWyhoYWxmX24gKyAxKTpuX2xldmVsc10pKQoKCiAgCiAgCiAgZ2dwbG90KGRmLCBhZXMoeD1xdWVyeV9icCx5PU4sZmlsbD10YXhvbiwgY29sb3IgPSB0YXhvbiwgZ3JvdXAgPSB0YXhvbikpICsKICAgIGdlb21fYXJlYShwb3NpdGlvbj0gcG9zaXRpb25fc3RhY2soKSkgKwogICAgI2dlb21fbGluZShwb3NpdGlvbj0nc3RhY2snKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSByZW9yZGVyZWRfY29sb3JzLCAKICAgICAgICAgICAgICAgICAgICAgIGFlc3RoZXRpY3MgPSBjKCdjb2xvdXInLCdmaWxsJyksCiAgICAgICAgICAgICAgICAgICAgICBndWlkZSA9ICdub25lJykgKwogICAgc2NhbGVfeF9sb2cxMChsYWJlbHMgPSBzY2FsZXM6OmxhYmVsX251bWJlcihzY2FsZV9jdXQgPSBzY2FsZXM6OmN1dF9zaSgnYnAnKSksCiAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IDEwMDAqcGFyc2VfbnVtYmVyKHVuaXF1ZShuX3NhbXBsZXNfZ2VuZXJhJHF1ZXJ5X2JwKSksCiAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IDEwMDAqcmFuZ2UocGFyc2VfbnVtYmVyKHVuaXF1ZShuX3NhbXBsZXNfZ2VuZXJhJHF1ZXJ5X2JwKSkpKSAgKwogICAgc2NhbGVfeV9jb250aW51b3VzKG4uYnJlYWtzID0gMTAsIG1pbm9yX2JyZWFrcyA9IHdhaXZlcigpKSArCiAgICBnZ3RpdGxlKHRpdGxlKSArCiAgICB5bGFiKCdOdW1iZXIgb2Ygc2FtcGxlcycpICsKICAgIHhsYWIoJ0Jhc2UgcGFpcnMgaW4gcXVlcnkgaW1hZ2VzJykgKwogICAgdGhlbWVfZmV3KCkgKwogICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoaGp1c3Q9MSxhbmdsZT00NSksCiAgICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSBOQSksCiAgICAgICAgICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfbGluZShjb2xvdXIgPSBncmF5KDAuNSkpLAogICAgICAgICAgICBwYW5lbC5ncmlkLm1pbm9yLnkgPSBlbGVtZW50X2xpbmUoY29sb3VyID0gZ3JheSgwLjYpLGxpbmV0eXBlID0gMiksCiAgICAgICAgICAgIHBhbmVsLm9udG9wID0gVFJVRSkKfQpgYGAKCmBgYHtyfQpOX3NwZWNpZXMgPSBwbG90X05zYW1wbGVzX2FyZWEobl9zYW1wbGVzX3NwZWNpZXMsdGl0bGU9J1N0aWdtYXBoeWxsb24gU3BlY2llcycpCk5fZ2VuZXJhID0gcGxvdF9Oc2FtcGxlc19hcmVhKG5fc2FtcGxlc19nZW5lcmEsdGl0bGU9J01hcGxpZ2hpYWNlYWUgYW5kIENocnlzb2JhbGFuYWNlYWUgR2VuZXJhJykKTl9mYW1pbGllcyA9IHBsb3RfTnNhbXBsZXNfYXJlYShuX3NhbXBsZXNfU1JBLHRpdGxlPSdTUkEgZmFtaWxlcycpCgpjb3dwbG90OjpwbG90X2dyaWQoTl9nZW5lcmEsTl9zcGVjaWVzLE5fZmFtaWxpZXMsIG5yb3cgPSAxKQpgYGAKClRvdGFsIG51bWJlciBvZiBTUkEgc2FtcGxlcy4gVmFsaWRhdGlvbjoKYGBge3J9CnJlYWRfY3N2KCd2YXJLb2Rlci9hbGxfU1JBL3ZhcmtvZGVyX3RyYWluZWRfbW9kZWxfTUwvaW5wdXRfZGF0YS5jc3YnKVstMV0gJT4lCiAgZ3JvdXBfYnkoaXNfdmFsaWQpICU+JQogIHN1bW1hcmlzZShOID0gbigpKQpgYGAKCgoKCg==